如果我声明一个空参数表的函数,然后给它传递参数,会发生什么?

11
例如,
#include <stdio.h>

void foo();

int main(void)
{
        foo();
        foo(42);
        foo("a string", 'C', 1.0);
        return 0;
}

void foo()
{
        puts("foo() is called");
}

输出:

foo() is called
foo() is called
foo() is called

这段代码可以成功编译(使用clang时没有警告)并且可以正常运行。但是我想知道传递给 foo() 的值会发生什么?它们是被推入栈中还是被丢弃了?

或许这个问题听起来毫无用处,但它确实有意义。例如,当我有 int main() 而不是 int main(void) 并且向其传递一些命令行参数时,main() 的行为是否会受到影响?

此外,在使用 <stdarg.h> 时,至少需要一个命名参数在 ... 之前由ISO C所要求。我们是否可以利用这样的声明 void foo() 来把从零到无限个参数传递给一个函数?

我刚刚注意到 void foo() 是一个“非原型声明”,而 void foo(void) 是一个“原型声明”。这与此有关吗?


澄清

似乎这个问题被标记为重复的What does an empty parameter list mean? [duplicate] (有趣的是,那个问题也是重复的...)。实际上,我不认为我的问题与那个问题有任何关系。它关注的是“在C中void foo()的含义”,但我知道这意味着“我可以向它传递任意数量的参数”,我也知道这是一个过时的功能。

但是这个问题非常不同。关键词是“What if”。我只想知道如果我向 void foo() 传递不同数量的参数,就像上面的示例代码一样,它们是否可以在 foo() 中使用?如果可以,如何做到这一点?如果不行,传递的参数是否有任何区别?这就是我的问题。


请注意,您不能使用无参函数来传递零个或多个变量参数,因为va_start()宏需要最后一个命名参数的名称,如果没有命名参数,它将无法运行。 - Jonathan Leffler
你说得对,<stdarg.h>至少需要一个参数。这就是为什么我想知道是否可以使用()的原因。顺便说一句:我已经澄清了这个问题,你能看一下吗? - nalzok
2
这些值被推入堆栈;当函数返回时,调用函数会清空堆栈(这是C语言的调用约定;其他语言使用不同的约定,因为被调用的函数知道它被传递了多少参数,或者用于传递参数的空间大小,并且可以因此清空堆栈)。没有一种可移植的方法来使用传递给函数(如foo())的参数。 - Jonathan Leffler
@JonathanLeffler 所以在实践中向 foo() 传递参数没有任何意义吗? - nalzok
1
没有任何意义。你甚至可能认为它是无意义的。 - Jonathan Leffler
1
@JonathanLeffler;我不认为这是一个重复的问题。 - haccks
2个回答

3
正如Jonathan Leffler所说,C的调用约定规定,调用函数(而不是被调用函数)负责从堆栈中弹出参数,因此即使参数与被调用函数期望的不匹配,程序也不会崩溃。
我想补充的是,C的调用约定还规定,参数按相反的顺序推入堆栈(例如,调用foo(1,2)先推2,然后推1)。这使得被调用函数即使不知道其余参数也可以访问前面的参数。例如,声明为int foo(int a,int b,...)的函数将能够访问ab ,即使不知道传递了哪些其他参数:ab在堆栈顶部。访问其他参数将需要使用堆栈指针黑客技巧,这就是printf所做的。当然,通过使用与期望不同的不同参数(例如printf(%d%d,3.5);),我们可以轻松获得有趣的结果。
因此,对于问题,是的,int foo()可以安全地使用任意数量/类型的参数进行调用,并且在1980年代,使用堆栈上的指针黑客技巧来访问未知参数被认为是“正常做法”。当可移植性和可读性变得越来越重要时,<stdarg.h>作为实现这些指针黑客技巧的可移植方式出现了(编译器将为目标平台生成正确的代码,因此它不再是一个“hack”)。但是,正如已经说过的那样,<stdarg.h>需要至少一个参数,因此它无法帮助int foo()

2
在 C 语言中,void foo() 声明了一个参数数量不确定的函数。函数的声明方式如下:function
return-type function-name(parameter-list,...) { body... }

parameter-list是用逗号分隔的函数参数列表。如果没有给出参数,则该函数不带任何参数,并应使用空括号或关键字void进行定义。如果在参数列表中的变量前面没有变量类型,则假定为int。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接