为什么在C中调用函数时允许传递的参数数量不足? 我知道函数原型在C++中是强制性的,如果函数是在()函数之后定义的,但是它是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; } #包括 int main() { printf(“总和为:%d”,加上(15.30));//哦,用点代替逗号 } 整数相加(整数a,整数b) { 返回a+b; }

为什么在C中调用函数时允许传递的参数数量不足? 我知道函数原型在C++中是强制性的,如果函数是在()函数之后定义的,但是它是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; } #包括 int main() { printf(“总和为:%d”,加上(15.30));//哦,用点代替逗号 } 整数相加(整数a,整数b) { 返回a+b; },c,printf,parameter-passing,function-call,C,Printf,Parameter Passing,Function Call,在上述程序中,如果在main()函数之前定义了add(int,int)函数,则程序肯定无法编译。这是因为调用函数时传递的参数少于所需的参数 但是,为什么上面的程序编译并运行得很好——将一些较大的垃圾值作为输出?原因是什么?是否最好使用函数原型,以便编译器检测类型不匹配以及与函数调用相关的任何其他错误 这是未定义的行为吗 为什么在C中调用函数时允许传递的参数数量不足 事实并非如此 编译器没有发出警告/出错的事实并不意味着它是正确的。如果C看到您调用一个尚未声明的函数,它将生成一个隐式声明,使用任意

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

但是,为什么上面的程序编译并运行得很好——将一些较大的垃圾值作为输出?原因是什么?是否最好使用函数原型,以便编译器检测类型不匹配以及与函数调用相关的任何其他错误

这是未定义的行为吗

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

事实并非如此


编译器没有发出警告/出错的事实并不意味着它是正确的。

如果C看到您调用一个尚未声明的函数,它将生成一个隐式声明,使用任意数量的参数,并假设返回
int
。在您的示例中,它正在创建
intadd()的隐式声明

在运行时,add函数将在其堆栈上接收一个double,并尝试以一种混乱且未定义的方式解释字节

C99改变了这一规则,并禁止调用未声明的函数,但大多数编译器仍然允许您这样做,尽管有警告


printf
是不相关的。

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

int add(a,b)
  int a;
  int b;
{
  return a+b;
}
给定
intx语句<代码>添加(3,5)将生成如下代码:

push 5
push 3
call _add
store r0,_x
编译器不需要知道任何关于
add
方法的返回类型(它可以猜测为
int
)以外的内容,就可以生成上述代码;如果该方法被证明存在,那么将在链接时发现该方法。提供的参数比例程预期的要多的代码会将这些值推送到堆栈上,在一段时间后代码弹出这些值之前,这些值将被忽略

若代码传递的参数太少、参数类型错误或两者兼而有之,就会出现问题。如果代码传递的参数少于例程预期的参数,那么如果被调用的代码没有使用后面的参数,这通常不会产生任何效果。如果代码读取未传递的参数,它们通常会读取“垃圾”值(尽管无法保证读取这些变量不会产生其他副作用)。在许多平台上,第一个未传递的参数将是返回地址,尽管并不总是如此。如果代码写入未传递的参数,通常会弄乱返回堆栈并导致奇怪的愚蠢行为

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

ANSI C标准化了功能原型的使用;如果编译器在看到对
add
的调用之前看到类似
intadd(inta,intb)
的定义或声明,那么它将确保传递两个
int
类型的参数,即使调用方提供了其他参数(例如,如果调用方传递了一个
double
,其值将在调用前强制键入
int
)。试图向编译器看到的方法传递太少的参数将导致错误

在上面的代码中,对
add
的调用是在编译器没有任何线索知道该方法需要什么之前进行的。虽然较新的方言不允许使用这种用法,但较旧的方言会让编译器在默认情况下假定
add
是一个方法,该方法将接受调用方提供的任何参数并返回
int
在上述情况下,被调用的代码可能希望堆栈上有两个字,但
double
常量可能会被推为两个或四个字。因此,该方法可能会从
double
值15.3的二进制表示形式中接收for
a
b
两个字

顺便说一句,如果隐式假定其类型的函数被声明为返回非
int
的函数,即使接受指定语法的编译器也几乎总是会发出嘎嘎声;如果使用非旧式语法定义此类函数,许多编译器也会发出嘎嘎声堆栈上的UMENT,较新的通常期望它们在寄存器中

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

double add();
然后代码
add(12.3,4.56)
将在堆栈上推送两个
double
值,并调用
add
函数,但如果其中一个函数声明:

double add(double a, double b);
然后,在许多系统上,代码
add(12.3,4.56)
会在调用之前加载浮点寄存器0和
12.3
以及浮点寄存器1和
4.56
(不推送任何内容)。因此,这两种声明样式不兼容。在此类系统上,尝试调用方法而不声明方法,然后在同一编译单元内使用新样式语法声明该方法,将产生编译错误。此外,某些此类系统会将带有寄存器参数的函数名作为前缀两个下划线和不带一个下划线的下划线