繁体   English   中英

将指针传递给与形式参数要求不符的函数

[英]Passing a pointer to a function that doesn't match the requirements of the formal parameter

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的函数的指针传递给编译器,为什么编译器不会抱怨呢?

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

为什么编译器不抱怨?

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

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

可能发生的情况是,编译器执行了通常要传递参数的任何操作(将其压入堆栈,将其放入指定的寄存器等)。 但是,使用错误数目的参数调用函数是未定义的行为,因此无法保证-程序可能崩溃,或者编译器会使猴子从你的鼻子里飞出来

假设您忽略了所有应该给您的编译器警告,那么您可以考虑发生以下情况:

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

显然,在您的平台上,发生了这样的情况: invalid的单独参数与valid的第一个参数位于相同的位置,因此invalid巧合地执行了您期望正确调用它的操作。 未清除参数的清除方式未指定-如果被调用函数应清除其参数空间,则将油炸堆栈,如果被调用函数被清除,则程序可能会继续运行。

无论您在这里调用未定义的行为。 尝试将func更改为单参数形式

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

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

你要:

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

与海湾合作委员会。 您的原始代码也给了我多个警告,顺便说一句-您使用的是哪个编译器? 如果您的问题确实是“为什么此代码似乎起作用?”,那么这就是未定义行为的乐趣之一。

这是未定义行为起作用的示例,
在您的示例中可能正在发生类似的情况。

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

无效函数中的第二个参数y会发生什么?

编译器仍会生成将func内的两个参数推入该行的代码

return f(x, y);

因为它没有更好的了。 您要调用带有两个参数的函数,它会推入两个参数。 (前提是原型允许这样做)。 如果检查了堆栈,则会看到它们,但是由于invalid只需要一个,因此您无法直接看到C中的第二个参数(没有欺骗性)。

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

要了解它为什么起作用,您需要了解一些有关汇编语言以及C如何使用堆栈传递参数的知识。 您可以将堆叠可视化为一大堆板块,其中每个板块都包含一个简单变量。 在许多平台上,所有参数都在堆栈上传递。 func将推yx ,调用f ,然后弹出变量。 通过查看堆栈上的两个顶部条目, valid加载xy invalid通过查看堆栈的顶部条目来找到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的原因。 它告诉编译器该函数实际上没有任何参数。 括号之间没有任何内容,它是一个带有隐式参数的函数。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM