C语言中函数调用约定的历史

C语言中函数调用约定的历史,c,function-prototypes,C,Function Prototypes,我记得,早期的C(例如K&R)允许在任何函数调用中传递任何内容,因此调用约定必须是从右向左推参数,调用方在函数返回后清除堆栈 我在一次演示中遇到了一个难题,解决方案涉及调用printf,而根本不使用任何头文件。他断言,在C语言中,如果您调用一个尚未声明的函数,那么编译器会隐式地将其参数列表作为它看到您传递的提升参数 但是,新的原型启用的函数调用是在ANSIC升级时引入的,它使用了一种更有效的调用约定,被调用的函数清除堆栈;每种用法都不重复 在我的记忆中,这两种形式被赋予了不同的链接器可见名称,并

我记得,早期的C(例如K&R)允许在任何函数调用中传递任何内容,因此调用约定必须是从右向左推参数,调用方在函数返回后清除堆栈

我在一次演示中遇到了一个难题,解决方案涉及调用
printf
,而根本不使用任何头文件。他断言,在C语言中,如果您调用一个尚未声明的函数,那么编译器会隐式地将其参数列表作为它看到您传递的提升参数

但是,新的原型启用的函数调用是在ANSIC升级时引入的,它使用了一种更有效的调用约定,被调用的函数清除堆栈;每种用法都不重复

在我的记忆中,这两种形式被赋予了不同的链接器可见名称,并且不兼容,这是在链接时捕获的。我坚持认为,他的示例是有效的,因为
printf
故意使用旧的表单,以支持在调用基础上传递任何内容

他说,这两种用途必须是兼容的,这是标准规定的。除非编译器总是生成旧式调用,否则我看不出它是如何工作的


根据标准,真实情况是什么?而且,这种情况的历史是什么?它随着时间的推移发生了变化吗?

C标准没有提到调用约定

从1989年的ANSI C标准(相当于1990年的ISO C标准)开始,在范围内没有正确声明的情况下调用变量函数,如
printf
,具有未定义的行为。该声明必须是原型,并且必须包含
,…
序列,以指示接受可变数量和类型的参数

从1999年的ISO C标准开始,调用没有可见声明的函数是违反约束的,需要进行诊断。在C99之前,被调用函数的返回类型是
int
,并且调用中出现任何(提升的)参数

许多C编译器将接受(可能带有警告)没有声明的调用,并且许多可能使用调用约定,在没有可见声明的情况下调用
printf
。但是该语言没有定义这样一个调用的行为,一个合格的编译器可以自由地拒绝它,或者生成任意错误的代码


如果要调用
printf
,只需在源文件顶部添加
#include
。这比思考对于一个给定的编译器你可能会得到什么要容易得多。

我投票将这个问题作为离题题来结束,因为这是一个更好的提问编程语言历史琐事的地方。我不确定你在问什么,但我认为简单的答案是,可变函数被视为没有原型的函数。自从C89被定义以来,在范围内没有原型的情况下调用像
printf()
这样的可变函数是不合法的。如果您真的想自己编写原型是合法的,但通常不可取。事实上,可以调用
printf
,而不使用
#include
,也不使用未定义的行为。您只需自己声明即可——正确:
intprintf(constchar*restrict format,…)
(如果声明错误,编译器不必抱怨。)此外,这涉及到一个问题,即变量函数的变量参数确实受到默认参数提升的影响,就像已声明但没有原型的函数的参数一样,过去隐式声明函数也是如此。不完全是这样,@JDługosz。这里有一个细微但重要的区别:函数声明不一定提供原型。后者描述了参数的数量及其类型,并在C90中引入。对于使用原型调用(声明的)函数和不使用原型调用(声明的)函数,有不同的规则(继续到C2011)。在C90中,它还允许调用在作用域中根本没有声明的函数,这些调用被解释为,如果函数在没有原型的情况下被声明为返回
int
@JDługosz的函数:如果声明正确,则是的——在C99和C11中也是一样的。C99中唯一改变的是必须有一个声明;该声明仍然不必是一个原型。(非原型声明和定义自1989年以来已经“过时”,但它们仍然没有从语言中删除。)如果调用一个非变量函数,其可见声明不是原型。参数的提升类型必须与定义给出的实际类型匹配。已确认,@KeithThompson。为我笨拙的措辞道歉。