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
会发生什么?
答案 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
将推送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
很重要。它告诉编译器该函数确实需要 no 参数。括号之间没有任何内容,它是一个带隐式参数的函数。