75

我不明白为什么这段代码会编译?

#include <stdio.h>
void foo() {
    printf("Hello\n");
}

int main() {
    const char *str = "bar";
    foo(str);
    return 0;
}

gcc 甚至没有警告我向 foo() 传递了太多参数。这是预期的行为吗?

4

4 回答 4

86

在 C 中,使用空参数列表声明的函数在被调用时接受任意数量的参数,这些参数受到通常的算术提升。调用者有责任确保提供的参数适用于函数的定义。

要声明一个带零参数的函数,您需要编写void foo(void);.

这是出于历史原因;最初,C 函数没有原型,因为 C 是从无类型语言B演变而来的。添加原型后,为了向后兼容,语言中保留了原始的无类型声明。

要让 gcc 对空参数列表发出警告,请使用-Wstrict-prototypes

如果在未指定参数类型的情况下声明或定义函数,则发出警告。(如果前面有一个指定参数类型的声明,则允许在没有警告的情况下进行旧式函数定义。)

于 2012-09-28T15:56:19.340 回答
54

由于遗留原因,使用参数列表声明函数()本质上意味着“在调用函数时找出参数”。要指定函数没有参数,请使用(void).

编辑:我觉得我在这个问题上因为老了而声名狼藉。只是为了让你们孩子知道编程曾经是什么样的,这是我的第一个程序。(不是 C;它向您展示了我们在此之前必须使用的内容。)

于 2012-09-28T15:54:12.227 回答
11
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 不强制实现为调用未定义行为的程序发出诊断,但您的程序仍然是错误程序。

于 2012-09-28T16:11:05.790 回答
6

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 Sec. 6.5.2.2):

如果表示被调用函数的表达式具有包含原型的类型,则参数的数量应与参数的数量一致。

但是,此约束不适用于这种情况,因为函数的类型不包括原型。但这是语义部分中的后续声明(不是“约束”),它确实适用:

如果表示被调用函数的表达式的类型不包含原型……如果参数的数量不等于参数的数量,则行为未定义。

因此,函数调用确实会导致未定义的行为(即,程序不是“严格符合”)。但是,该标准仅要求实现在违反实际约束时报告诊断消息,在这种情况下,没有违反约束。因此,gcc 不需要报告错误或警告即可成为“符合要求的实现”。

所以我认为这个问题的答案,为什么 gcc 允许它?,是 gcc 不需要报告任何内容,因为这不是违反约束。此外,gcc 并未声称报告所有类型的未定义行为,即使使用 -Wall 或 -Wpedantic 也是如此。这是未定义的行为,这意味着实现可以选择如何处理它,而 gcc 选择在没有警告的情况下编译它(显然它只是忽略了参数)。

于 2014-07-14T15:50:49.400 回答