带参数但不带类型指示器的C函数是否仍然有效?

带参数但不带类型指示器的C函数是否仍然有效?,c,C,代码如下: 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@ubu

代码如下:

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遇到“旧式”函数时,它符合旧的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-墙体x.c&./a.out”进行测试)

或者引用这些评论:“在K&R C中,调用具有任意多个参数的函数是非常好的,因为省略号符号当时还没有发明。”


请注意,编译器可以显示任意多的额外警告,并且仍然符合标准。例如,苹果的编译器警告此代码。

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

zeroparam.c:2: warning: type of ‘param111’ defaults to ‘int’ zeroparam.c:2:警告:“param111”的类型默认为“int”
这告诉您,没有类型的参数在默认情况下将由一个整数表示。正如定义一个没有返回类型的函数一样,它也将默认为
int

我可以解释为什么这样做,但不能解释为什么编译器不警告它

有几种方法可以指定参数的排序方式和放置位置。C调用约定允许在没有副作用的情况下传递额外参数,因为调用方清理这些参数,而不是调用的函数,并且它们都在堆栈上传递:

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

“func”只知道一个值,它从末尾取它们,因此
param111==10


然后,“main”知道两个参数被推送,将它们检索回来,从而清理堆栈。

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

#包括
void*func(参数111)
字符*参数111;
{
printf(“%s\n”,参数111);
返回参数111;
}
int main()
{
void*bla0=func();
void*bla1=func(99);
void*bla2=func(11111110,99);
printf(“%p,%p,%p\n”,bla0,bla1,bla2);
返回0;
}

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

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

C11 6.9.1功能定义第9节

如果在没有参数的情况下定义了接受可变数量参数的函数 如果类型列表以省略号符号结尾,则行为未定义

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

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

代码中的
func
函数只有函数定义,没有函数声明符。在C996.5.2.2(函数调用)中,它统计:

“不会隐式执行其他转换;特别是 参数不会与以下函数定义中的参数进行比较: 不包括函数原型声明器。“

调用
func(10,99)
func(11111110,9910001)
时,编译器不会将参数的数量和类型与函数定义中的参数进行比较。您甚至可以通过
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”的参数太多


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

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

所谓的K&R C,1978年第1版的克尼汉和里奇的经典著作中描述的语言,没有函数原型。(原型是指定其参数类型的函数声明。)函数定义仍然必须定义其参数(可能是impli
int printf( const char *format ,...);
int func(int);
int func(param111)
{
    printf("%d\n", param111);
    return param111;
}
int func(param111)
int param111;
{
    printf("%d\n", param111);
    return param111;
}
#include <stdio.h>
#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);
}