views:

112

answers:

6
int valid (int x, int y) {
    return x + y;
}

int invalid (int x) {
    return x;
}

int func (int *f (int, int), int x, int y) { 
    //f is a pointer to a function taking 2 ints and returning an int
    return f(x, y);
}

int main () {
    int val = func(valid, 1, 2),
        inval = func(invalid, 1, 2); // <- 'invalid' does not match the contract 

    printf("Valid:   %d\n", val);
    printf("Invalid: %d\n", inval);

    /*  Output:
     *  Valid:   3
     *  Invalid: 1
     */
}

At the line inval = func(invalid, 1, 2);, why am I not getting a compiler error? If func expects a pointer to a function taking 2 ints and I pass a pointer to a function that takes a single int, why isn't the compiler complaining?

Also, since this is happening, what happens to the second parameter y in the invalid function?

+1  A: 

You want:

int func (int (*f) (int, int), int x, int y) { 

What you have in your code is the type of a function that returns an int * - you want a pointer to a function that returns an int. With that change, this line:

 inval = func(invalid, 1, 2);

gives me:

fp.c:16: warning: passing argument 1 of 'func' from incompatible pointer type
fp.c:9: note: expected 'int (*)(int,  int)' but argument is of type 'int (*)(int)'

with gcc. Your original code gave me multiple warnings too, BTW - which compiler are you using? And if your question is really "Why does this code seem to work?", well that's one of the joys of undefined behaviour.

anon
Neil, can you elaborate a bit more on that please?
Andreas Grech
What you said is true in the sense that in my case, `f` is a function returning a pointer rather than a pointer to a function. But, even when I changed it to `(*f)`, there where still no problems as regards running the code.
Andreas Grech
+1  A: 

why isn't the compiler complaining?

Maybe you need a better compiler? gcc says warning: passing argument 1 of ‘func’ from incompatible pointer type on this code.

Also, since this is happening, what happens to the second parameter y in the invalid function?

Probably what happens is that the compiler does whatever it would normally do to pass a parameter (push it on the stack, put it in a designated register, etc). Calling a function with the wrong number of parameters is undefined behavior, though, so there are no guarantees - the program could crash, or the compiler could make monkeys fly out of your nose.

David Gelhar
A: 

what happens to the second parameter y in the invalid function?

The compiler still generates code that pushes two arguments inside func for the line

return f(x, y);

since it doesn't know any better. You want to call a function with two arguments, it pushes two. (provided the prototype allows it, which it does). If you inspected the stack, you'd see them, but since invalid only takes one, you have no direct way of seeing the second argument in C (without trickery).

Alex
+1  A: 

Assuming you're ignoring all the compiler warnings that this should give you, you can think about what happens like this:

Your code is attempting to call a function which takes two ints, and returns one. Depending on the calling convention the parameters might be passed in registers on the cpu or on the stack, the output probably goes to a register. The valid call works fine, everything is where it's expected. For the invalid call the same stack is set up, with the two parameters as that's what the program thinks it's calling, then the function is called.

Apparently with your platform it so happens that the solitary argument for invalid is in the same location as the first parameter for valid, so that invalid coincidentally does what you would have expected calling it properly. How the parameters are cleaned up is unspecified - if the called function is supposed to clean up the space for its parameters your stack will be fried, if the calling function cleans up then your program will possibly continue to function.

Irregardless you are invoking undefined behaviour here. Try changing func to the single parameter form

int func(int(*f)(int),x){return f(x);}

and see if both calls still work.

Scott Wales
+1  A: 

Here is an example where undefined behavior works,
and probably something similar is happening in your example.

typedef int (*p)(int, int);
typedef int (*p2)(int);

int invalid (int x) {
    return x;
}

int func () {
   p2 f = invalid;
   return ((p)f)(1, 2);
}

// IA32 asm, "func"
...
216:     p2 f = invalid;
00402148   mov         dword ptr [ebp-4],offset @ILT+1380(invalid) (00401569)
0040214F   mov         eax,dword ptr [ebp-4]
00402152   mov         dword ptr [ebp-4],eax
217:     return ((p)f)(1, 2);
00402155   mov         esi,esp
00402157   push        2 ; <--
00402159   push        1 ; <--
0040215B   call        dword ptr [ebp-4] ; "invalid" will use only "1"
0040215E   add         esp,8 ; <-- `pop` the arguments
...
Nick D
A: 

In C, it is not an error to cast a pointer from one type to another. However, a good compiler will generate a warning when passing the wrong pointer type to a function without an explicit cast. If you're not getting a warning, I strongly recommend checking your compiler settings to make sure warnings are turned on. Or consider using a different compiler. ;-)

To understand why it works, you need to understand a bit about assembly language and how C uses the stack to pass parameters. You can visualize the stack as a big stack of plates, where each plate holds one simple variable. On many platforms, all parameters are passed on the stack. func will push y and x, call f, then pop the variables back off. valid loads x and y by looking at the two top entries on the stack. invalid finds x by looking at the top entry on the stack.

Here's what the stack might look like inside invalid:

main:     3
          uninitialized
f:        2
          1
          invalid
invalid:  2
          1

invalid() takes one parameter, so it just looks at the top of the stack (the 1) and loads it as the parameter.

This is also how functions like printf work. They can accept a variable number of parameters. The first parameter is at the top of the stack, and they can just keep looking down the stack for however many parameters they need. Some systems pass some parameters in registers instead of using the stack, but it works analogously.

In the very early days of C, function declarations did not include parameters at all. In fact, if you declare a function without anything between the parenthesis, you can still define a function that way. For example, this compiles just fine:

void foo();
void bar(void) {
        foo(5); /* foo's parameters are implicit */
}

That's why it's important to include the void when declaring a function without parameters. It tells the compiler that the function really takes no parameters. With nothing between the parentheses, it's a function with implicit parameters.

Daniel Stutzbach
"include the void when declaring a function with parameters." - I think you mean "without parameters".
Andreas Grech
@Andreas Fixed, thanks!
Daniel Stutzbach