为什么这在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的答复: