我不明白为什么要编译此代码?
#include <stdio.h>
void foo() {
printf("Hello\n");
}
int main() {
const char *str = "bar";
foo(str);
return 0;
}
gcc甚至没有发出警告,我向foo()传递了太多参数。这是预期的行为吗?
我不明白为什么要编译此代码?
#include <stdio.h>
void foo() {
printf("Hello\n");
}
int main() {
const char *str = "bar";
foo(str);
return 0;
}
gcc甚至没有发出警告,我向foo()传递了太多参数。这是预期的行为吗?
void foo(void)
void foo(void)
,则编译器通常应给出错误,而不仅仅是警告(尽管官方措辞仅要求“可诊断”)。
Answers:
在C语言中,用空参数列表声明的函数在调用时接受任意数量的参数,这些参数受常规算术提升的影响。调用者有责任确保提供的参数适合于函数的定义。
要声明一个带有零参数的函数,您需要编写void foo(void);
。
这是出于历史原因;最初,C函数没有原型,因为C是从无类型语言B演变而来的。添加原型时,原始的无类型声明保留在该语言中,以便向后兼容。
要让gcc警告参数列表为空,请使用-Wstrict-prototypes
:
在未指定参数类型的情况下声明或定义函数时发出警告。(如果在声明函数之前指定了参数类型,则可以在没有警告的情况下使用旧式函数定义。)
出于遗留原因,()
使用参数列表声明函数本质上意味着“在调用函数时确定参数”。要指定一个函数没有参数,请使用(void)
。
编辑:我觉得我在这个老问题上赢得了声誉。为了让您的孩子知道编程的过去,这是我的第一个程序。(不是C;它向您展示了在此之前我们必须使用的工具。)
void foo() {
printf("Hello\n");
}
foo(str);
在C语言中,此代码不违反约束(如果使用原型形式定义,void foo(void) {/*...*/}
则不会),并且由于不存在违反约束的情况,因此不需要编译器发出诊断信息。
但是,根据以下C规则,该程序具有未定义的行为:
从:
(C99,6.9.1p7)“如果声明符包括参数类型列表,则该列表还指定所有参数的类型;这样的声明符还用作函数原型,以供以后在同一转换单元中调用同一函数。如果声明者包括一个标识符列表,142)参数的类型应在随后的声明列表中声明。”
该foo
功能未提供原型。
从:
(C99,6.5.2.2p6)“如果表示被调用函数的表达式的类型不包含原型,则参数的个数不等于参数的个数,则行为不确定。”
该foo(str)
函数的调用是不确定的行为。
C没有强制实现对发出未定义行为的程序发出诊断,但是您的程序仍然是错误的程序。
C99标准(6.7.5.3)和C11标准(6.7.6.3)均规定:
标识符列表仅声明函数参数的标识符。作为该函数定义的一部分的函数声明器中的空列表指定该函数没有参数。不属于该函数定义的一部分的函数声明器中的空列表指定不提供有关参数数量或类型的信息。
由于foo的声明是定义的一部分,因此该声明指定foo接受0个参数,因此调用foo(str)至少在道德上是错误的。但是如下所述,C语言中的“错误”程度不同,并且编译器在处理某些类型的“错误”方面可能有所不同。
举一个简单的例子,考虑以下程序:
int f() { return 9; }
int main() {
return f(1);
}
如果我使用Clang编译以上内容:
tmp$ cc tmp3.c
tmp3.c:4:13: warning: too many arguments in call to 'f'
return f(1);
~ ^
1 warning generated.
如果我使用gcc 4.8进行编译,即使使用-Wall,也不会收到任何错误或警告。先前的答案建议使用-Wstrict-prototypes,它可以正确地报告f的定义不是原型形式,但这实际上不是重点。C标准允许使用非原型形式的函数定义,例如上述一种,并且标准明确指出此定义指定该函数接受0个参数。
现在有一个约束条件(C11第6.5.2.2节):
如果表示被调用函数的表达式的类型包括原型,则参数的数量应与参数的数量一致。
但是,此约束不适用于这种情况,因为函数的类型不包括原型。但是,以下是语义部分中的后续语句(不是“约束”),它确实适用:
如果表示被调用函数的表达式的类型不包含原型,则...如果参数数量与参数数量不相等,则行为未定义。
因此,函数调用的确会导致未定义的行为(即程序不是“严格符合”的)。但是,该标准仅要求实现在违反实际约束时报告诊断消息,在这种情况下,不存在违反约束的情况。因此,不需要gcc报告错误或警告即可成为“符合要求的实现”。
所以我认为问题的答案(为什么gcc允许它?)是gcc不需要报告任何东西,因为这不是约束违规。而且,即使使用-Wall或-Wpedantic,gcc也不会声称报告每种未定义的行为。这是未定义的行为,这意味着实现可以选择如何处理它,并且gcc选择了不进行警告的编译(显然,它只是忽略了该参数)。
...
,但是获取...
函数的参数值与获取“常规”函数的参数值不同。