为什么在C语言中这是合法的?

16

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

我正在尝试完善C语言中符号解析的语义,并使用此测试用例来验证,已经在常规编译器clang和gcc上进行了尝试。

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

大多数编译器似乎只会警告关于额外参数的问题。

问题:这背后的基本原理是什么?

对于我的符号表生成/解析阶段,我考虑将函数作为一个带有返回类型和若干参数(根据语法)的符号。

谢谢。


这个程序并不严格符合规范,请看我的回答。 - ouah
4个回答

32

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

如果您真的想要零个参数,则应该这样写:

void foo (void);

空列表变量是古老的C语言遗留问题,甚至在ANSI介入之前就存在。例如:

add_one(val)
int val;
{
    return val + 1;
}
(其中int是默认的返回类型,参数类型在声明符外面指定。)
如果你正在做一个玩具编译器,并且不担心与C99的每一个细节保持一致,那么我建议你放弃这个选项并要求使用某种形式的参数列表。
这将使你的生活变得更加轻松,而且我质疑人们使用那个“特性”的必要性。

很好的附注。我不知道C语言有这个。 - 0xC0DEFACE
谢谢,@Jerry,好久没写这样的代码了 :-) - paxdiablo
2
@paxdiablo:当然。我记得,因为偶尔我会决定用代码高尔夫的形式表达我的疯狂,而一个参数可以让你使用比普通定义少5个字符来定义一个本地的int。 :-) - Jerry Coffin
现在我在思考是否应该从语言标准或编译器中移除那些古老的特性,除非是在维护刻在石碑上的3000年代码,否则没有人会真正使用它们,以使它们更易于维护。 - cthulhu
但是,@pax,代码高尔夫怎么办?K&R风格的声明是必不可少的! - dmckee --- ex-moderator kitten
显示剩余2条评论

17

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

int foo();
long bar();

等等。这告诉编译器名称指的是一个函数,但没有指定参数的数量或类型。最初(1989年)C标准中最大的改变可能就是添加“函数原型”,它允许声明参数的数量和类型,因此当您调用函数时,编译器可以检查您传递了什么。为了保持现有代码的兼容性,他们决定空参数列表将保留其现有含义,如果您想声明不带参数的函数,则必须在参数列表的位置上添加voidint f(void);

请注意,在C++中情况并非如此-- C++消除了旧式函数声明,并要求指定所有参数的数量和类型1。如果您声明函数而没有参数,则意味着它不接受任何参数,如果您尝试传递任何参数,编译器会发出警告(除非您还重载了该函数,以便还有另一个带有相同名称的函数可以接收参数)。

1虽然您仍然可以使用省略号表示接受可变参数列表的函数--但是如果您这样做,只能传递POD类型作为参数。


1
恭喜,Jerry,你是我2000个赞的骄傲获得者 :-) - paxdiablo
感谢您提及变量参数列表。 - Zak

5

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

如果您这样书写:

void foo(void) {}

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

gcc 的 -Wstrict-prototypes 将会捕获此类错误。如果需要将其作为错误处理,可以使用 -Werror=strict-prototypes。标准并没有明确规定这应该是一个警告还是一个错误。


4

为什么在C语言中这是合法的?

首先需要澄清的是,C标准并没有使用“合法”这个词。

在C语言术语中,这个程序不是严格符合规范的

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

编译此程序时,由于函数调用foo(5)没有违反任何约束条件,因此不需要进行诊断。但是,使用参数调用函数foo会引发未定义行为。与调用未定义行为的任何程序一样,它不是严格符合规范的,编译器有权拒绝翻译该程序。
在C标准中,带有空参数列表的函数声明表示该函数具有未指定数量的参数。但是,带有空参数列表的函数定义表示该函数没有参数。
以下是C标准中相关的段落(所有强调都属于我):

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

C标准中说明foo(5)调用是未定义行为的段落如下:
如果表示被调用函数的表达式具有不包括原型的类型,则对每个参数执行整数提升,并将类型为float的参数提升为double。这些称为默认参数提升。如果参数数量不等于参数数量,则行为未定义。根据(C99, 6.9.1p7),我们知道foo的定义没有提供原型。如果声明符包括参数类型列表,则该列表还指定所有参数的类型;这样的声明符也用作以后在同一翻译单元中调用同一函数的函数原型。如果声明符包括标识符列表,则必须在后续声明列表中声明参数的类型。请参见委员会对缺陷报告#317的答案,以获取关于此主题的权威答案:

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


一个能够使用不正确参数执行函数调用的程序并不是严格符合标准的,但是如果这些函数调用从未被执行,那么一个严格符合标准的程序可能包含这样的函数调用;因此,这样的函数调用的存在可能不会阻止符合标准的编译器正确处理包含它们的程序。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接