将指针传递给与formal参数的要求不匹配的函数

时间:2010-05-04 10:32:19

标签: c pointers function function-pointers

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
     */
}

inval = func(invalid, 1, 2);行,为什么我没有收到编译错误?如果func期望一个函数的指针占用2个int并且我将指针传递给一个带有单个int的函数,为什么编译器不抱怨?

此外,由于这种情况发生了,y函数中的第二个参数invalid会发生什么?

6 个答案:

答案 0 :(得分:2)

  

为什么编译器不抱怨?

也许您需要更好的编译器? gcc在此代码上说warning: passing argument 1 of ‘func’ from incompatible pointer type

  

此外,由于这种情况发生了,无效函数中的第二个参数y会发生什么?

可能发生的事情是编译器会执行通常传递参数的任何操作(将其推入堆栈,将其放入指定的寄存器等)。但是,使用错误数量的参数调用函数是未定义的行为,因此无法保证 - 程序可能会崩溃,或者编译器可能会生成monkeys fly out of your nose

答案 1 :(得分:2)

假设你忽略了这应该给你的所有编译器警告,你可以考虑这样的事情:

您的代码正在尝试调用一个需要两个整数的函数,并返回一个。根据调用约定,参数可能会在cpu或堆栈的寄存器中传递,输出可能会转到寄存器。 valid调用正常,一切都在预期的位置。对于invalid调用,设置了相同的堆栈,其中两个参数就是程序认为它正在调用的参数,然后调用该函数。

显然,在您的平台上,invalid的单独参数与valid的第一个参数位于同一位置,因此invalid恰巧符合您的预期称之为正确。如何清除参数是未指定的 - 如果被调用的函数应该清理其参数的空间,那么堆栈将被炸掉,如果调用函数清理掉,那么程序可能会继续运行。

无论你在这里调用未定义的行为。尝试将func更改为单个参数表单

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

并查看两个电话是否仍然有效。

答案 2 :(得分:1)

你想:

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

代码中的内容是返回int *的函数的类型 - 您希望指向返回int的函数的指针。有了这个改变,这一行:

 inval = func(invalid, 1, 2);

给了我:

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)'

用gcc。您的原始代码也给了我多个警告,BTW - 您正在使用哪个编译器?如果你的问题真的是“为什么这段代码似乎有效呢?”,那就是未定义行为的乐趣之一。

答案 3 :(得分:1)

以下是未定义行为的作用
的示例 在你的例子中可能发生了类似的事情。

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
...

答案 4 :(得分:0)

  

什么   发生在第二个参数y in   无效的功能?

编译器仍会生成代码,在func内为行

推送两个参数
return f(x, y);

因为它不知道更好。你想调用一个带有两个参数的函数,它会推动两个。 (如果原型允许它,它会这样做)。如果您检查了堆栈,您会看到它们,但由于invalid只接受一个,因此您无法直接查看C中的第二个参数(没有欺骗)。

答案 5 :(得分:0)

在C中,将指针从一种类型转换为另一种类型并不是错误。但是,一个好的编译器会在将错误的指针类型传递给没有显式强制转换的函数时生成警告。如果您没有收到警告,我强烈建议您检查编译器设置以确保警告已打开。或者考虑使用不同的编译器。 ; - )

要理解它的工作原理,您需要了解汇编语言以及C如何使用the stack传递参数。您可以将堆栈可视化为一大堆板,其中每个板都有一个简单的变量。在许多平台上,所有参数都在堆栈上传递。 func将推送yx,调用f,然后退回变量。 valid通过查看堆栈中的两个顶部条目来加载xyinvalid通过查看堆栈顶部条目找到x

这里的堆栈可能看起来像无效:

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

invalid()接受一个参数,因此它只是查看堆栈的顶部(1)并将其作为参数加载。

这也是printf等函数的工作原理。他们可以接受可变数量的参数。第一个参数位于堆栈的顶部,他们可以继续向下查看堆栈,无论它们需要多少参数。有些系统在寄存器中传递一些参数而不是使用堆栈,但它的工作方式类似。

在C的早期阶段,函数声明根本不包含参数。实际上,如果在括号之间声明一个没有任何内容的函数,你仍然可以用这种方式定义一个函数。例如,这编译得很好:

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

这就是为什么在声明没有参数的函数时包含void很重要。它告诉编译器该函数确实需要 no 参数。括号之间没有任何内容,它是一个带隐式参数的函数。