【问题标题】:Why is this legal in C?为什么这在 C 中是合法的?
【发布时间】:2011-07-15 12:13:00
【问题描述】:

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

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

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

大多数编译器似乎只对无关参数发出警告。

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

在我的符号表生成/解析阶段,我考虑将函数视为具有返回类型和几个参数化参数(基于语法)的符号,每个参数都有各自的类型。

谢谢。

【问题讨论】:

  • 这是程序不严格符合,看我的回答

标签: c language-design


【解决方案1】:

在原型中具有 no 列出参数的函数被视为具有 indeterminate 数字,而不是零。

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

void foo (void);

空列表变体是古代 C 语言的遗留物,甚至在 ANSI 开始使用它之前,你就有了类似的东西:

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

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

如果您正在做一个玩具编译器,并且您不担心符合 C99 的每一个小部分,我会放弃该选项并需要某种参数列表.

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

【讨论】:

  • 很好的旁注。我对 C 不了解。
  • 这并不重要,但你的函数头是错误的。应该是:add_one(val) int val; [...] 不过,参数也可以利用默认的 int 规则,因此您可以消除 int val;(但如果它不是 int,您仍然需要,例如,long val;) .
  • 谢谢,@Jerry,我已经有一段时间没有写过这样的代码了 :-)
  • @paxdiablo:当然。我记得,因为每隔一段时间我决定以代码高尔夫的形式表达我的精神错乱,并且一个参数允许您使用比正常定义少 5 个字符的本地 int 定义。 :-)
  • 现在我想知道是否应该从语言标准或编译器中删除那些没有人真正使用过的古老特性,除非他们维护在石板上雕刻的 3000 年历史代码,以使它们更易于维护。
【解决方案2】:

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

如果你写了:

void foo(void) {}

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

gcc 的-Wstrict-prototypes 会捕捉到这一点。对于错误,请使用-Werror=strict-prototypes。该标准从不指定某事应该是警告还是错误。

【讨论】:

    【解决方案3】:

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

    int foo();
    long bar();
    

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

    请注意,在 C++ 中也是如此不是 - C++ 消除了旧式函数声明,并要求指定所有参数的数量和类型1支持>。如果你声明的函数没有参数,这意味着它不接受任何参数,如果你尝试传递任何参数,编译器会报错(除非你也重载了函数,所以还有另一个同名的函数 可以带参数)。

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

    【讨论】:

    • 恭喜,杰瑞,你是我第 2000 次投票的骄傲赢家 :-)
    • @paxdiablo:谢谢——这是一种荣幸。
    • 感谢提及可变参数列表
    【解决方案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) 调用是未定义行为的段落是这样的:

    (C99, 6.5.2.2p6) "如果表示被调用函数的表达式的类型不包含原型,则对每个参数执行整数提升,并且参数具有浮点类型被提升为双精度。这些被称为默认参数 促销。 如果参数的数量不等于参数的数量,则 行为未定义。”

    从 (C99, 6.9.1p7) 开始,我们知道foo 的定义没有提供原型。

    (C99, 6.9.1p7) "如果声明器包含一个参数类型列表,该列表还指定了所有参数的类型;这样的声明器还可以作为函数原型,以便以后在同一函数中调用相同的函数翻译单元。如果声明器包含标识符列表,则参数的类型应在以下声明列表中声明。

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

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

    【讨论】:

    • 一个可以执行带有错误参数的函数调用的程序不是严格符合的,但是一个严格符合的程序可能包含这样的函数调用,如果它们从未被执行;因此,仅仅存在此类函数调用可能不会阻止符合标准的编译器正确处理包含它们的程序。
    猜你喜欢
    • 2019-03-25
    • 2017-12-12
    • 2023-03-17
    • 2020-08-06
    • 2019-04-29
    • 2011-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多