C带有无类型指示的参数function仍然有效吗?

代码如下:

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()将接受任何输入。 但是这个代码如何编译并成功运行?

此行为是为了向后兼容该语言的旧版本,即该语言的K&R版本。 当GCC遇到“旧式”function时,它符合旧的K&R C行为,这意味着在这种情况下没有警告。

实际上,如果将函数更改为: int func(int param111) ,则会获得预期的警告:

 xc: In function 'main': xc:11:5: error: too many arguments to function 'func' xc:2:5: note: declared here xc:12:5: error: too many arguments to function 'func' xc:2:5: note: declared here xc:14:1: warning: control reaches end of non-void function [-Wreturn-type] 

(经GCC 4.7.3和“gcc -std = c99 -Wall xc && ./a.out”测试)

或者从评论中引用JeremyP : “在K&R C中,使用尽可能多的参数来调用函数是完全没问题的,因为省略号符号当时并未被发明。”

请注意,编译器可以显示它想要的更多额外警告,并且仍然符合标准。 例如Apple的编译器警告这个代码。

函数声明被解释为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  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; } 

尽管显然有不正确的参数类型和计数。

正如其他人所解释的那样,它被解释为K&R C. 值得注意的是,它在ANSI C中是未定义的行为:

C11 6.9.1 function定义第9节

如果定义了接受可变数量参数的函数而没有以省略号表示法结尾的参数类型列表,则行为未定义。

所以变量数参数函数必须以...作为参数结束,如printf

 int printf( const char *format ,...); 

我可以解释一下,为什么这有效,但不是为什么编译器不会对此发出警告。

有一些调用约定 ,它们指定参数的排序方式和放置位置。 C调用约定允许传递额外的参数而没有副作用,因为调用者清理它们,而不是调用函数,并且它们都在栈上传递:

对于func(10,99)的情况,“main”按以下顺序(从右到左)将值推送到堆栈:

 99 10 

“func”只知道一个值,它从最后得到它们,所以param111 == 10

然后“main”,知道推出了两个参数,将它们取回,从而清理堆栈。

代码中的func函数只有函数定义,但没有函数声明符。 在C99 6.5.2.2(函数调用 )中,它的统计信息:

“没有其他转换是隐式执行的;特别是,参数的数量和类型不会与函数定义中不包含函数原型声明符的参数的数量和类型进行比较。”

func(10,99)func(11111110, 99, 10001) ,编译器不会将参数的数量和类型与函数定义中的参数进行比较。 你甚至可以通过func("abc")调用它。 但是,如果在代码中添加以下func函数声明:

 int func(int); 

int fun(int)声明是因为C99标准会隐式地将para111int类型),编译器会发送以下错误:

zeroparam.c:在函数’main’中:
zeroparam.c:15:13:错误:函数’func’的参数太多
zeroparam.c:6:5:注意:在这里宣布
zeroparam.c:16:17:错误:函数’func’的参数太多

顺便说一句:我不认为这是一个“K&R程序”问题,因为你在命令中明确指定了“-std = c99”。

如果您没有收到该代码的任何警告,那是因为您的编译器没有强制执行C99规则(调用printf或任何函数,没有可见的声明是违反约束的)。 您可以通过将正确的选项传递给编译器来获得至少一些警告。 如果您正在使用gcc,请尝试gcc -std=c99 -pedantic -Wall -Wextra

所谓的K&R C,1978年第1版Kernighan和Ritchie的经典着作The C Programming Language所描述的语言 ,没有函数原型。 (原型是一个函数声明,它指定了它的参数类型。)函数定义仍然必须定义它的参数(可能是隐式的),但声明没有 – 并且典型的编译器没有检查参数的正确匹配(在函数调用)参数(在函数定义中)。

如果您使用错误的数字和/或类型的参数调用函数,会发生什么情况并不完全清楚。 在现代术语中,它是未定义的行为,但较旧的编译器通常会让你玩弄技巧。

1989 ANSI C标准(重新发布为1990 ISO C标准)引入了原型(从早期的C ++中借用),但并不需要它们。 但它确实明确规定调用具有错误数量或类型的参数的函数会导致未定义的行为 ; 编译器不需要警告你,但是当你运行它时程序可以做任何事情。

1999 ISO C标准删除了“隐式int”规则,并且调用没有可见声明的函数使其成为非法( 违反约束 ) – 但它仍然允许旧式函数声明和定义。 所以根据K&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  

到源文件的顶部,因为你正在调用printf 。 根据C89 / C90规则,调用没有可见声明的printf具有未定义的行为(因为printf采用可变数量的参数)。 在C99及更高版本下,它是一个约束违规,需要编译时诊断。

我一直在挑选缺少的参数声明。 程序略有改动的变体:

 #include  /* 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(以及你正在使用的任何编译器)的作者认为,用旧式声明和/或定义来警告不匹配的函数调用是不值得的。

如果在编译时检查警告,则会看到以下消息:

 zeroparam.c:2:警告:'param111'的类型默认为'int'

这告诉你没有类型的参数默认为整数。 与定义没有返回类型的函数一样,它也将默认为int