函数调用的参数多于预期

问题描述 投票:6回答:2

我正在审查一些代码,我发现了类似的东西。

文件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它编译,链接和正常工作,而不会在过程中抛出一个警告。这是为什么?

谢谢!

c parameter-passing calling-convention
2个回答
9
投票

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

push    11
push    10
call    _bar
add     esp, 8

bar只会“看到”10,并将按预期运行该参数,返回100。之后堆栈恢复,所以main也没有错位;如果你刚刚通过了10,那么它将增加4到esp

对于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:4用于Windows上的MSVC; 6在System V ABI上


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与非匹配参数一起使用,就会出现编译错误。

© www.soinside.com 2019 - 2024. All rights reserved.