代码如下:
int func(param111)
{
printf("%d\n", param111);
return param111;
}
int main()
{
int bla0 = func(99);
int bla1 = func(10,99);
int bla2 = func(11111110,99,10001);
printf("%d, %d, %d\n", bla0, bla1, bla2);
}
编译结果:
zbie@ubuntu:~$ gcc -Wall -g -std=c99 -O2 zeroparam.c
zeroparam.c: In function ‘func’:
zeroparam.c:2: warning: type of ‘param111’ defaults to ‘int’
运行结果:
zbie@ubuntu:~$ ./a.out
99
10
11111110
99, 10, 11111110
我知道如果带有零参数的func,例如int func()接受任何输入,代码应该没问题。但是这个代码如何编译并成功运行?
答案 0 :(得分:19)
此行为是为了向后兼容该语言的旧版本,即该语言的K& R版本。当GCC遇到“旧式”功能时,它符合旧的K& R C行为,这意味着在这种情况下没有警告。
的确,如果您将功能更改为:int func(int param111)
,则执行会获得预期的警告:
x.c: In function ‘main’:
x.c:11:5: error: too many arguments to function ‘func’
x.c:2:5: note: declared here
x.c:12:5: error: too many arguments to function ‘func’
x.c:2:5: note: declared here
x.c:14:1: warning: control reaches end of non-void function [-Wreturn-type]
(经GCC 4.7.3和“gcc -std = c99 -Wall x.c&& ./a.out”测试)
或者从评论中引用JeremyP:“在K& RC中,使用尽可能多的参数调用函数是完全没问题的,因为省略号表示法当时并未发明。”
请注意,编译器可以显示所需的额外警告,但仍符合标准。例如,Apple的编译器就此代码发出警告。
答案 1 :(得分:8)
函数声明被解释为K& R样式函数声明,因为它缺少类型。在标准中,这被称为带有标识符列表的函数声明,而不是通常声明中的参数类型列表。
根据C99规范6.9.1 / 7,只有带有参数类型列表的函数定义才被认为是函数原型。 K& R样式使用标识符列表,因此不被认为具有原型。
不检查没有原型的函数的函数调用参数计数或类型(根据6.5.2.2 / 8,“参数的数量和类型不与不包含函数的函数定义中的参数的参数数量和类型进行比较原型声明者“)。因此,使用任何数量和类型的参数调用以K& R样式声明的函数是合法的,但是对于6.5.2.2/9,使用无效类型的调用将产生未定义的行为。
作为示例,以下内容将在没有任何警告的情况下编译(在gcc -Wall -Wextra -pedantic -std=c99 -O
上):
#include <stdio.h>
void *func(param111)
char *param111;
{
printf("%s\n", param111);
return param111;
}
int main()
{
void *bla0 = func();
void *bla1 = func(99);
void *bla2 = func(11111110,99);
printf("%p, %p, %p\n", bla0, bla1, bla2);
return 0;
}
尽管显然有不正确的参数类型和计数。
答案 2 :(得分:2)
正如其他人所解释的那样,它被解释为K&amp; R C.值得注意的是,它在ANSI C中是未定义的行为:
C11 6.9.1 功能定义第9节
如果定义了接受可变数量参数的函数而没有参数 以省略号表示法结尾的类型列表,行为未定义。
因此,变量数参数函数必须以...
作为参数结束,如printf
:
int printf( const char *format ,...);
答案 3 :(得分:1)
我可以解释一下,为什么会这样,但不是为什么编译器不会警告它。
有一些calling conventions,它们指定参数的排序方式和放置位置。 C调用约定允许传递额外的参数而没有副作用,因为调用者清理它们,而不是调用函数,并且它们都在栈上传递:
对于func(10,99)的情况,“main”按以下顺序(从右到左)将值推送到堆栈:
99
10
“func”只知道一个值,它从最后得到它们,所以param111 == 10
。
然后“main”,知道推出了两个参数,将它们取回,从而清理堆栈。
答案 4 :(得分:1)
代码中的func
函数只有函数定义,但没有函数声明符。在C99 6.5.2.2(函数调用)中,它是统计信息:
“没有隐式执行其他转换;特别是,数量和类型 参数不与函数定义中的参数进行比较 不包括函数原型声明器。“
当调用func(10,99)
和func(11111110, 99, 10001)
时,编译器不会将参数的数量和类型与函数定义中的参数进行比较。您甚至可以通过func("abc")
拨打电话。但是,如果在代码中添加func
的以下函数声明:
int func(int);
(int fun(int)
被声明是因为C99标准会隐式地将para111
提升为int
类型),编译器会发送以下错误:
zeroparam.c:在函数'main'中:
的参数太多了
zeroparam.c:15:13:错误:函数'func'的参数太多了 zeroparam.c:6:5:注意:在这里宣布
zeroparam.c:16:17:错误:函数'func'
BTW:我不认为这是一个“K&amp; R程序”问题,因为你在命令中明确指定了“-std = c99”。
答案 5 :(得分:0)
如果您没有收到该代码的任何警告,那是因为您的编译器没有强制执行C99规则(调用printf
或任何函数,没有可见的声明是违反约束的)。您可以通过将正确的选项传递给编译器来获得至少一些警告。如果您正在使用gcc,请尝试gcc -std=c99 -pedantic -Wall -Wextra
。
所谓的K&amp; R C,即1978年第1版Kernighan和Ritchie的经典着作The C Programming Language所描述的语言,没有函数原型。 (原型是一个函数声明,指定其参数的类型。)函数定义仍然必须定义其参数(可能是隐式的),但声明没有 - - 典型的编译器没有检查参数(在函数调用中)与参数(在函数定义中)的正确匹配。
如果您使用错误的数字和/或类型的参数调用函数,那么会发生什么情况并不完全清楚。在现代术语中,它是未定义的行为,但较旧的编译器通常会让你玩弄技巧。
1989 ANSI C标准(重新发布为1990 ISO C标准)引入了原型(借鉴了早期的C ++),但并不需要它们。但它确实明确规定调用具有错误数量或类型的参数的函数会导致未定义的行为;编译器不需要警告你,但是当你运行它时程序可以做任何事情。
1999年的ISO C标准放弃了&#34;隐含的int&#34;规则并使其成为非法的(约束违反)来调用没有可见声明的函数 - 但它仍然允许旧式函数声明和定义。因此,在K&amp; R1和C89 / C90规则下,您的函数定义为:
int func(param111)
{
printf("%d\n", param111);
return param111;
}
有效,param111
的类型为int
。根据C99规则,它无效,但是:
int func(param111)
int param111;
{
printf("%d\n", param111);
return param111;
}
仍然合法(即使符合2011年标准,仍然合法)。
从C99和C11开始,如果你调用一个其可见声明不是原型的函数,那么完全由你决定是否正确;如果你弄错了,编译器就不需要警告你。
这就是总是为所有函数声明和定义使用原型的原因。如今,编写使用ANSI前编译器编译的代码的需求几乎不存在;很难找到一个至少不支持C89 / C90的编译器。
哦,你需要添加
#include <stdio.h>
到源文件的顶部,因为您正在调用printf
。根据C89 / C90规则,调用没有可见声明的printf
具有未定义的行为(因为printf
采用可变数量的参数)。在C99及更高版本下,它违反了约束,需要编译时诊断。
我一直在挑选缺失的参数声明。程序略有改动的变体:
#include <stdio.h> /* add this line */
int func(param111)
int param111; /* add this line */
{
printf("%d\n", param111);
return param111;
}
int main(void) /* add "void" */
{
int bla0 = func(99);
int bla1 = func(10,99);
int bla2 = func(11111110,99,10001);
printf("%d, %d, %d\n", bla0, bla1, bla2);
}
不违反任何需要在C90,C99或C11中进行编译时诊断的规则 - 但对func
的第二次和第三次调用都有未定义的行为。
请注意,编译器实际上有足够的信息来警告您对func
的调用不正确。它刚刚看到了func
的定义,并且它应该知道任何不能正确传递一个可隐式转换为int
的类型的参数的调用是无效。不需要警告,但编译器可以随时打印他们喜欢的任何额外警告。显然,gcc(以及你正在使用的任何编译器)的作者认为,用旧式声明和/或定义来警告不匹配的函数调用是不值得的。
答案 6 :(得分:-2)
如果在编译时检查警告,则会看到以下消息:
zeroparam.c:2: warning: type of ‘param111’ defaults to ‘int’
这告诉你没有类型的参数默认为整数。与定义不带返回类型的函数一样,它也将默认为int
。