具有比预期更多的参数的函数调用

时间:2018-11-22 15:25:14

标签: c parameter-passing calling-convention

我正在查看一些代码,但遇到类似的情况。

文件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可以编译,链接和正常工作,而不会在过程中发出任何警告。为什么会这样?

谢谢!

2 个答案:

答案 0 :(得分:9)

这实际上取决于调用约定和体系结构。例如,在x86上的cdecl中,参数从右向左推,并且调用者恢复了堆栈,附加参数的存在对函数bar是透明的:

push    11
push    10
call    _bar
add     esp, 8

bar将仅“看到” 10,并且将与该参数一起正常工作,并返回100。此后将恢复堆栈,因此main中也不会出现未对齐的情况;如果您刚刚通过了10,它将在esp上加上4。

对于MSVC on WindowsSystem 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与不匹配的参数一起使用,就会出现编译错误。