为什么这在C中合法?

我正在为我的大学编写一个编译器/语言课程的玩具C编译器。

我试图在C中充实符号解析的语义,并提出了我尝试对常规编译器clang&gcc的测试用例。

void foo() { } int main() { foo(5); } // foo has extraneous arguments 

大多数编译器似乎只是警告无关的论点。

问题:这背后的根本原因是什么?

对于我的符号表生成/解析阶段,我正在考虑将函数作为具有返回类型的符号,以及几个参数化参数(基于语法),每个参数都具有相应的类型。

谢谢。

原型中没有列出参数的函数被认为具有不确定的数字,而不是零。

如果你真的想要零参数,它应该是:

 void foo (void); 

空列表变体是古代C的延续,甚至在ANSI获得它之前,你有这样的东西:

 add_one(val) int val; { return val + 1; } 

int是默认的返回类型和声明符外部指定的参数类型)。

如果你正在做一个玩具编译器并且你并不担心每一小块C99的符合性,我只是抛出那个选项并需要某种参数列表。

它会让你的生活变得更加轻松,我怀疑人们是否需要使用这个“function”。

它是为了与古老的 C编译器向后兼容。 在地球冷却之前,所有C函数声明看起来大致如下:

 int foo(); long bar(); 

等等。 这告诉编译器该名称是指一个函数,但没有指定任何有关参数数量或类型的信息。 原始(1989)C标准中最大的变化可能是添加了“函数原型”,它允许声明参数的数量和类型,因此编译器可以检查调用函数时传递的内容。 为了保持现有代码的兼容性,他们决定一个空参数列表将保留其现有含义,如果你想声明一个不带参数的函数,你必须添加void代替参数列表: int f(void);

请注意,在C ++中,情况并非如此 – C ++消除了旧样式函数声明,并要求指定所有参数的数量和类型1 。 如果你声明没有参数的函数,这意味着它不接受任何参数,如果你试图传递任何参数,编译器会抱怨(除非你也重载了函数,所以有另一个具有相同名称的函数可以带参数)。

1虽然您仍然可以使用省略号作为带有变量参数列表的函数 – 但是当/如果这样做,您只能将POD类型作为参数传递。

您还没有为foo函数提供原型,因此编译器无法强制执行它。

如果你写道:

 void foo(void) {} 

那么你将提供一个不带参数的函数原型。

gcc的-Wstrict-prototypes会抓住这个。 对于错误,请使用-Werror=strict-prototypes 。 标准从未指定某些内容应该是警告还是错误。

为什么这在C中合法?

首先只是澄清C标准不使用legal这个词。

在C术语中,该程序并不严格符合

 void foo() { } int main() { foo(5); } // foo has extraneous arguments 

编译此程序时,由于函数调用foo(5) ,不需要诊断:没有约束违规。 但是使用参数调用函数foo调用未定义的行为。 与任何调用未定义行为的程序一样,它并不严格符合,编译器有权拒绝翻译程序。

在C标准中,带有空参数列表的函数声明意味着该函数具有未指定数量的参数。 但是带有空参数列表的函数定义意味着该函数没有参数。

以下是C标准中的相关段落(全部强调我的):

(C99,6.7.5.3p14)“标识符列表仅声明函数参数的标识符。函数声明符中的空列表是该函数定义的一部分, 指定该函数没有参数。

表示foo(5)调用的C标准的段落是未定义的行为是这样的:

(C99,6.5.2.2p6)“ 如果表示被调用函数的表达式具有不包含原型的类型,则对每个参数执行整数提升,并将具有float类型的参数提升为double。这些被调用默认参数提升。 如果参数数量不等于参数数量,则行为未定义 。“

从(C99,6.9.1.1p7),我们知道foo的定义不提供原型。

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

有关该主题的权威答案,请参阅委员会对缺陷报告#317的答复:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_317.htm