我正在查看一些代码,但遇到类似的情况。
文件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
可以编译,链接和正常工作,而不会在过程中发出任何警告。为什么会这样?
谢谢!
答案 0 :(得分:9)
这实际上取决于调用约定和体系结构。例如,在x86上的cdecl
中,参数从右向左推,并且调用者恢复了堆栈,附加参数的存在对函数bar
是透明的:
push 11
push 10
call _bar
add esp, 8
bar
将仅“看到” 10
,并且将与该参数一起正常工作,并返回100
。此后将恢复堆栈,因此main
中也不会出现未对齐的情况;如果您刚刚通过了10
,它将在esp
上加上4。
对于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:Windows上的MSVC为4;在系统V ABI上为6
答案 1 :(得分:2)
通常,您将具有以下文件结构:
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
与不匹配的参数一起使用,就会出现编译错误。