22

代码如下:

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() 它将接受任何输入,则代码应该没问题。但是这段代码是如何编译并成功运行的呢?

4

7 回答 7

21

此行为是为了向后兼容旧版本的语言,即语言的 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 xc && ./a.out”测试)

或者从评论中引用JeremyP的话:“在 K&R C 中,调用具有任意数量参数的函数是非常好的,因为那时还没有发明省略号表示法。” .

请注意,编译器可以显示尽可能多的额外警告,并且仍然符合标准。例如,Apple 的编译器会对此代码发出警告。

于 2013-08-13T06:38:52.790 回答
9

函数声明被解释为 K&R 风格的函数声明,因为它缺少类型。在标准术语中,这称为带有标识符列表的函数声明,而不是通常声明中的参数类型列表

根据 C99 规范 6.9.1/7,只有带有参数类型列表的函数定义才被认为是函数原型。K&R 样式改为使用标识符列表,因此不被视为具有原型。

对没有原型的函数的函数调用不会检查参数计数或类型(根据 6.5.2.2/8,“参数的数量和类型不会与不包含函数原型声明符的函数定义中的参数的数量和类型进行比较” )。因此,使用任何数量和类型的参数调用以 K&R 样式声明的函数是合法的,但根据 6.5.2.2/9,使用无效类型的调用将产生未定义的行为。

作为说明,以下代码将在没有任何警告的情况下编译(on 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;
}

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

于 2013-08-13T06:50:57.243 回答
2

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

C11 6.9.1函数定义第 9 节

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

因此,可变数量参数函数必须以...as 参数结尾,例如printf

int printf( const char *format ,...);
于 2013-08-13T07:15:06.750 回答
1

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

所谓的 K&R C,即 1978 年第一版 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 <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,它应该知道,任何没有传递完全 1 个可隐式转换为的类型的参数的调用int都是无效的。不需要警告,但编译器总是可以打印他们喜欢的任何额外警告。显然 gcc 的作者(以及您正在使用的任何编译器)认为不值得努力警告对具有旧式声明和/或定义的函数的不匹配调用。

于 2013-08-13T15:33:13.733 回答
1

我可以解释为什么这有效,但不能解释为什么编译器不警告它。

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

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

99
10

"func" 只知道一个值,它从最后获取它们,所以param111 == 10.

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

于 2013-08-13T06:49:48.770 回答
1

代码中的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”的参数太多</p>

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

于 2013-08-13T07:38:51.037 回答
-2

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

zeroparam.c:2:警告:“param111”类型默认为“int”

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

于 2013-08-13T06:40:31.110 回答