[英]Undefined behavior when passing a pointer to a function with a formal parameter of pointer to const?
[英]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
将推y
和x
,调用f
,然后弹出变量。 通过查看堆栈上的两个顶部条目, valid
加载x
和y
。 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.