为什么gcc允许将参数传递给定义为不带参数的函数?

red*_*gon 73 c gcc compiler-errors

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

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

int main() {
    const char *str = "bar";
    foo(str);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

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

eca*_*mur 83

在C中,使用空参数列表声明的函数在被调用时接受任意数量的参数,这些参数受常规算术促销的约束.调用者有责任确保提供的参数适合函数的定义.

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

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

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

如果声明或定义函数而不指定参数类型,则发出警告.(如果前面有一个指定参数类型的声明,则允许使用旧式函数定义而不发出警告.)


Eri*_*hil 53

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

编辑:我觉得我因为年老而在这个问题上声名狼借.只是让孩子们知道以前编程是什么样的,这是我的第一个程序.(不是C;它告诉你我们之前必须使用的东西.)

  • 类型规则,面向对象,封装,重载和其他现代语言功能并未在一夜之间发展.几十年前,编程语言更加原始.和实验.C是对它之前的进步,并且功能参数的强类型化或者如何实现它们的原因尚不清楚.当时的主要动机是为程序员提供简单的方法来完成强大的功能.使用强类型来减少错误的需求并不是那么大的动力. (5认同)
  • @Drise:你不会,不再了.但是,在1989年标准之前,C语言不支持原型声明(声明参数的数量和类型); 你只能在声明中指定函数的返回类型.如果您在声明中更改了空参数列表的规则,那么遗留代码会有很多遗留代码,所以它仍然受支持,但新代码应该*在声明函数时始终*使用原型语法. (4认同)
  • 在类型安全方面,早期的C只是B的推进.有很多安全类型的语言(Simula,Pascal,甚至是Algol),没有问题强制执行功能参数的强类型.通过在资源有限的小型计算机上实现更高效和更容易实现C赢得了胜利,但当时所涉及的权衡是显而易见的. (3认同)
  • 等等,你认真吗?你为什么要这样做? (2认同)

oua*_*uah 10

void foo() {
    printf("Hello\n");
}

foo(str);
Run Code Online (Sandbox Code Playgroud)

在C中,此代码不违反约束(如果它以原型形式定义void foo(void) {/*...*/}),并且由于没有约束违规,编译器不需要发出诊断.

但是根据以下C规则,此程序具有未定义的行为:

从:

(C99,6.9.1.1p7)"如果声明符包含参数类型列表,则列表还指定所有参数的类型;这样的声明符也可用作函数原型,以便稍后调用同一转换单元中的同一函数.如果声明者包含一个标识符列表,142)参数的类型应在下面的声明列表中声明."

foo功能不提供原型.

从:

(C99,6.5.2.2p6)"如果表示被调用函数的表达式具有不包含原型的类型[...]如果参数数量不等于参数数量,则行为未定义."

foo(str)函数的调用是不确定的行为.

C不强制实现为调用未定义行为的程序发出诊断,但您的程序仍然是错误的程序.


Ste*_*gel 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);
}
Run Code Online (Sandbox Code Playgroud)

如果我使用Clang编译上面的代码:

tmp$ cc tmp3.c
tmp3.c:4:13: warning: too many arguments in call to 'f'
  return f(1);
         ~  ^
1 warning generated.
Run Code Online (Sandbox Code Playgroud)

如果我使用gcc 4.8进行编译,即使使用-Wall,我也不会收到任何错误或警告.之前的回答建议使用-Wstrict-prototypes,它正确地报告f的定义不是原型形式,但这不是重点.C标准允许以非原型形式的函数定义,例如上面的标准,并且标准清楚地声明该定义指定该函数采用0参数.

现在有一个约束(C11 Sec.5.5.2.2):

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

但是,在这种情况下,此约束不适用,因为函数的类型不包括原型.但是这里是语义部分中的后续语句(不是"约束"),它确实适用:

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

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

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