为什么在C中调用函数时允许传递的参数数量不足?

Des*_*tor 7 c printf parameter-passing function-call

我知道函数原型在C++中是强制性的,如果函数是在main()函数之后定义的,但它在C中是可选的(但推荐).我最近编写了一个简单的程序,它执行了2个数字的加法,但错误地使用了点运算符代替传递参数时的逗号.

#include <stdio.h>
int main()
{
    printf("sum is: %d",add(15.30)); // oops, uses dot instead of comma
}

int add(int a,int b)
{
    return a+b;
}
Run Code Online (Sandbox Code Playgroud)

在上面的程序中,如果在add(int,int)函数之前定义了main()函数,那么程序肯定无法编译.这是因为调用函数时传递的参数少于所需的参数.

但是,为什么上面的程序编译运行正常 - 给出一些大的垃圾值作为输出?是什么原因?使用函数原型设计是否更好,以便编译器检测到类型不匹配以及与函数调用相关的任何其他错误?

这是未定义的行为吗?

sup*_*cat 10

在C的早期,当方法看起来像:

int add(a,b)
  int a;
  int b;
{
  return a+b;
}
Run Code Online (Sandbox Code Playgroud)

给出int x;一个语句add(3,5);将生成如下代码:

push 5
push 3
call _add
store r0,_x
Run Code Online (Sandbox Code Playgroud)

编译器不需要知道任何关于add超越的返回类型(它可以猜测的方法int,以便产生上面的代码); 如果该方法存在,那将在链接时发现.提供了比预期的例程更多参数的代码会将这些值推送到堆栈中,在这些值中它们将被忽略,直到代码在一段时间之后弹出它们.

如果代码传递的参数太少,错误类型的参数或两者都有问题,则会出现问题.如果代码传递的参数少于例程所期望的参数,那么如果被调用的代码没有使用后面的参数,则通常没有效果.如果代码读取未传递的参数,它们通常会读取"垃圾"值(尽管不能保证读取这些变量的尝试不会产生其他副作用).在许多平台上,第一个未传递的参数将是返回地址,但并非总是如此.如果代码写入了未传递的参数,那通常会使返回堆栈陷入混乱并导致奇怪的愚蠢行为.

传递错误类型参数通常会导致被调用函数在其参数的错误位置查找; 如果传递的类型小于预期的类型,则可能导致被调用的方法访问它不拥有的堆栈变量(与传递的参数太少的情况一样).

ANSI C标准化了函数原型的使用; 如果编译器int add(int a, int b)在看到调用之前看到定义或声明add,那么它将确保它传递两个类型的参数int,即使调用者提供其他内容(例如,如果调用者通过a double,其值将被强制键入int在电话会议之前).尝试将过少的参数传递给编译器看到的方法将导致错误.

在上面的代码中,调用add是在编译器有任何线索期望的方法之前进行的.虽然较新的方言不允许这样的使用,但较旧的方言会让编译器默认add采用这种方法来接受调用者提供和返回的任何参数int.在上面的例子中,被调用的代码可能会期望在堆栈上推送两个单词,但是double常量可能会被推送为两个或四个单词.因此,该方法可能从值15.3 的二进制表示中接收ab两个单词double.

顺便说一句,即使是一个接受指定语法的编译器,如果一个隐含假定类型的函数被声明为返回除了int; 如果使用除旧式语法之外的任何其他函数定义此类函数,许多人也会发出尖叫声.虽然第一个C编译器总是在堆栈上推送函数参数,但是较新的编译器通常期望它们在寄存器中.因此,如果有人要申报

 /* Old-style declaration for function that accepts whatever it's given
    and returns double */

double add();
Run Code Online (Sandbox Code Playgroud)

然后代码add(12.3,4.56)double在堆栈上推送两个值并调用该add函数,但如果有人声明:

double add(double a, double b);
Run Code Online (Sandbox Code Playgroud)

然后在许多系统上,代码add(12.3,4.56)将在调用之前加载浮点寄存器0 12.3和浮点寄存器1 4.56(不推送任何东西).因此,两种声明风格兼容.在这样的系统上,尝试在不声明方法的情况下调用方法,然后使用新式语法在同一编译单元中声明它将产生编译错误.此外,一些此类系统的前缀是带有两个下划线的寄存器参数的函数的名称和不带一个下划线的函数的名称,因此如果使用新式语法定义方法但在没有编译器看到原型的情况下调用该方法,则编译器_add即使函数被调用,也会尝试调用__add.这将导致链接器拒绝该程序,而不是生成不起作用的可执行文件.


Rya*_*ing 9

如果C看到你调用一个尚未声明的函数,它将生成一个隐式声明,接受任意数量的参数,并假设返回int.在你的例子中,它正在创建隐式声明int add(); .当然,这是错误的.

在运行时,add函数将在其堆栈上接收单个double,并尝试以令人困惑和未定义的方式解释字节.

C99更改了此规则并禁止调用未声明的函数,但大多数编译器仍允许您执行此操作,尽管有警告.

printf是无关紧要的.