我正在审查一些代码,我发现了类似的东西。
文件foo.c:
int bar(int param1)
{
return param1*param1;
}
文件main.c:
#include <stdio.h>
int bar(int param1, int unusedParam);
int main (void)
{
int param = 2, unused = 0;
printf("%d\n", bar(param, unused));
}
运行gcc main.c foo.c -Wall --pedantic -O0
它编译,链接和正常工作,而不会在过程中抛出一个警告。这是为什么?
谢谢!
这实际上取决于调用约定和体系结构。例如,在x86上使用cdecl
,其中参数从右向左推,并且调用者恢复堆栈,附加参数的存在对函数bar
是透明的:
push 11
push 10
call _bar
add esp, 8
bar
只会“看到”10
,并将按预期运行该参数,返回100
。之后堆栈恢复,所以main
也没有错位;如果你刚刚通过了10
,那么它将增加4到esp
。
对于MSVC on Windows和System V ABI的x64调用约定也是如此,其中前几个1整数参数在寄存器中传递;第二个参数将通过main
中的调用填充到其指定的寄存器中,但bar
甚至没有查看。
但是,如果您尝试使用备用调用约定,其中被调用者负责清理堆栈,则在构建阶段或运行时(更糟糕)会遇到麻烦。例如,stdcall
用参数列表使用的字节数装饰函数名,所以我甚至无法通过改变bar
来使用stdcall
链接最终的可执行文件:
error LNK2019: unresolved external symbol _bar@8 referenced in function _main
这是因为bar
现在在其目标文件中具有签名_bar@4
,就像它应该的那样。
如果您使用过时的调用约定pascal
,其中参数从左向右推送,这会很有趣:
push 10
push 11
call _bar
现在bar
像你预期的那样返回121而不是100。也就是说,如果函数成功返回,那么它将不会,因为被调用者应该清理堆栈但由于额外的参数而失败,因此废弃了返回地址。
1:4用于Windows上的MSVC; 6在System V ABI上
通常你有这个文件结构:
foo.c的
#include "foo.h"
int bar(int param1)
{
return param1*param1;
}
foo.h中
int bar(int param1);
main.c中
#include <stdio.h>
#include "foo.h"
int main (void)
{
int param = 2, unused = 0;
printf("%d\n", bar(param, unused));
}
现在,只要将bar
与非匹配参数一起使用,就会出现编译错误。