参数比预期更多的函数调用
Function call with more parameters than expected
我在审查一些代码时遇到了类似的情况。
文件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
将仅 "see" 10
,并且将按预期使用该参数 returning 100
。堆栈随后恢复,因此 main
中也没有错位;如果您刚刚通过了 10
,它会在 esp
中加上 4。
这两个 MSVC on Windows and the System V ABI 的 x64 调用约定也是如此,其中前几个 1 个整数参数在寄存器中传递;第二个参数将通过 main
中的调用填充到其指定的寄存器中,但 bar
.
甚至不会查看
但是,如果您尝试使用被调用方负责清理堆栈的备用调用约定,您将 运行 在构建阶段或(更糟)在 运行时间。 stdcall
,例如,用参数列表使用的字节数修饰函数名称,所以我什至无法通过将 bar
更改为使用 link 最终的可执行文件 stdcall
改为:
error LNK2019: unresolved external symbol _bar@8 referenced in function _main
这是因为 bar
现在在其对象文件中有签名 _bar@4
,这是应该的。
如果您使用过时的调用约定 pascal
,这会变得很有趣,其中参数从左到右推送:
push 10
push 11
call _bar
现在 bar
return 是 121,而不是 100,正如您预期的那样。也就是说,如果函数成功 returns,它不会,因为被调用者应该清理堆栈但由于额外参数而失败,破坏了 return 地址。
1:Windows 上的 MSVC 为 4;系统 V ABI 上的 6
通常你会有这样的文件结构:
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
和不匹配的参数,就会出现编译错误。
我在审查一些代码时遇到了类似的情况。
文件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
将仅 "see" 10
,并且将按预期使用该参数 returning 100
。堆栈随后恢复,因此 main
中也没有错位;如果您刚刚通过了 10
,它会在 esp
中加上 4。
这两个 MSVC on Windows and the System V ABI 的 x64 调用约定也是如此,其中前几个 1 个整数参数在寄存器中传递;第二个参数将通过 main
中的调用填充到其指定的寄存器中,但 bar
.
但是,如果您尝试使用被调用方负责清理堆栈的备用调用约定,您将 运行 在构建阶段或(更糟)在 运行时间。 stdcall
,例如,用参数列表使用的字节数修饰函数名称,所以我什至无法通过将 bar
更改为使用 link 最终的可执行文件 stdcall
改为:
error LNK2019: unresolved external symbol _bar@8 referenced in function _main
这是因为 bar
现在在其对象文件中有签名 _bar@4
,这是应该的。
如果您使用过时的调用约定 pascal
,这会变得很有趣,其中参数从左到右推送:
push 10
push 11
call _bar
现在 bar
return 是 121,而不是 100,正如您预期的那样。也就是说,如果函数成功 returns,它不会,因为被调用者应该清理堆栈但由于额外参数而失败,破坏了 return 地址。
1:Windows 上的 MSVC 为 4;系统 V ABI 上的 6
通常你会有这样的文件结构:
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
和不匹配的参数,就会出现编译错误。