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


75

我不明白为什么要编译此代码?

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


21
欢迎使用C语言,这是旧版标准的一部分。仅供参考,C ++不支持。
Steve-o

9
如果将函数声明为void foo(void)
Hans Passant 2012年

6
@HansPassant:如果将其声明为void foo(void),则编译器通常应给出错误,而不仅仅是警告(尽管官方措辞仅要求“可诊断”)。
杰里·科芬

3
编译时添加-Wall
pyCthon 2012年

1
@reddragon看到我的答案
ecatmur 2012年

Answers:


85

在C语言中,用空参数列表声明的函数在调用时接受任意数量的参数,这些参数受常规算术提升的影响。调用者有责任确保提供的参数适合于函数的定义。

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

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

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

在未指定参数类型的情况下声明或定义函数时发出警告。(如果在声明函数之前指定了参数类型,则可以在没有警告的情况下使用旧式函数定义。)


53

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

编辑:我觉得我在这个老问题上赢得了声誉。为了让您的孩子知道编程的过去,这是我的第一个程序。(不是C;它向您展示了在此之前我们必须使用的工具。)


2
等一下,你是认真的吗?你为什么要那样做?
Drise

5
类型规则,面向对象,封装,重载和其他现代语言功能并不是一overnight而就的。几十年前,编程语言更加原始。和实验。C是它之前的一个进步,并且尚不清楚为什么要大量键入函数参数或如何实现它们的原因。当时的主要动机是为程序员提供简便的方法来执行强大的功能。那时,使用强类型来减少错误的需求并不是很大的动机。
埃里克·波斯特皮希尔

4
@Drise:您不会,现在不再。但是,在1989年标准之前,C语言不支持原型声明(声明了参数的数量和类型)。您只能在声明中指定函数的返回类型。有一个很大的遗留代码在那里,如果你在声明中改变了规则的参数列表为空,所以它仍然支持该会打破,但新的代码应该总是声明函数时使用的原型语法。
约翰·博德

3
在类型安全性方面,早期的C仅比B先进。存在许多安全类型化的语言(Simula,Pascal,甚至Algol),在强制执行功能参数的强类型方面没有问题。C通过在资源受限的小型计算机上更高效,更容易实现而赢得了胜利,但当时的权衡是显而易见的。
ecatmur 2012年

1
@John Bode关于1989年前的C是正确的。IIRC也被称为TraditionalC。允许将int用作char的另一个强大“功能”。我的第一个C编译器是传统的C,它教会了我爱ANSI C.
JQA

10

在C语言中,此代码不违反约束(如果使用原型形式定义,void foo(void) {/*...*/}则不会),并且由于不存在违反约束的情况,因此不需要编译器发出诊断信息。

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

从:

(C99,6.9.1p7)“如果声明符包括参数类型列表,则该列表还指定所有参数的类型;这样的声明符还用作函数原型,以供以后在同一转换单元中调用同一函数。如果声明者包括一个标识符列表,142)参数的类型应在随后的声明列表中声明。”

foo功能未提供原型。

从:

(C99,6.5.2.2p6)“如果表示被调用函数的表达式的类型不包含原型,则参数的个数不等于参数的个数,则行为不确定。”

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

C没有强制实现对发出未定义行为的程序发出诊断,但是您的程序仍然是错误的程序。


6

C99标准(6.7.5.3)和C11标准(6.7.6.3)均规定:

标识符列表仅声明函数参数的标识符。作为该函数定义的一部分的函数声明器中的空列表指定该函数没有参数。不属于该函数定义的一部分的函数声明器中的空列表指定不提供有关参数数量或类型的信息。

由于foo的声明是定义的一部分,因此该声明指定foo接受0个参数,因此调用foo(str)至少在道德上是错误的。但是如下所述,C语言中的“错误”程度不同,并且编译器在处理某些类型的“错误”方面可能有所不同。

举一个简单的例子,考虑以下程序:

如果我使用Clang编译以上内容:

如果我使用gcc 4.8进行编译,即使使用-Wall,也不会收到任何错误或警告。先前的答案建议使用-Wstrict-prototypes,它可以正确地报告f的定义不是原型形式,但这实际上不是重点。C标准允许使用非原型形式的函数定义,例如上述一种,并且标准明确指出此定义指定该函数接受0个参数。

现在有一个约束条件(C11第6.5.2.2节):

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

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

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

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

所以我认为问题的答案(为什么gcc允许它?)是gcc不需要报告任何东西,因为这不是约束违规。而且,即使使用-Wall或-Wpedantic,gcc也不会声称报告每种未定义的行为。这是未定义的行为,这意味着实现可以选择如何处理它,并且gcc选择了不进行警告的编译(显然,它只是忽略了该参数)。


在C89之前的日子里,大多数C编译器都会忽略任何未使用的参数,并允许调用者忽略与未使用的参数关联的参数。正确调用约定的验证通常很有用,但是某些现有的API可能要求函数以不同数量和类型的参数传递。使用的常规处理方式...,但是获取...函数的参数值与获取“常规”函数的参数值不同。
超级猫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.