为什么“auto”关键字对于 C 语言的编译器编写者有用?

Chi*_*roh 40 c compiler-construction keyword auto automatic-storage

我目前正在阅读“专家 C 编程 - 深层 C 秘密”,刚刚发现了这个:

\n
\n

auto永远不需要存储类说明符。这对于编译器编写者来说最有意义\n在符号表 \xe2\x80\x94 中创建一个条目,它表示“此存储在进入\n块时自动分配”(而不是在编译时静态分配,或在编译时动态分配)堆)。auto\n对于所有其他程序员来说几乎没有意义,因为它只能在函数内部使用,但是\n函数中的数据声明默认具有此属性。

\n
\n

我看到有人在这里问了同样的事情,但他们没有任何答案,评论中给出的链接仅解释了为什么 C 中有这样一个继承自B 的关键字,以及与 C++ 的差异11 或 C++11 之前版本。

\n

无论如何,我发布的内容是为了重点说明auto关键字在编译器编写中以某种方式有用的部分,但是这个想法是什么以及与符号表的联系是什么?

\n

我确实坚持这样一个事实:我只询问用 C 语言编写编译器时的潜在用法(而不是编写 C 编译器)。

\n

为了澄清这一点,我问这个问题是因为我想知道是否有一个auto可以证明合理的代码示例,因为作者在编写编译器时表示会有。

\n

这里的重点是我认为已经理解了auto(继承自B,它是强制性的,但在C中无用),但我无法想象使用它时的任何示例是有用的(或者至少不是无用的)。

\n

看起来确实没有任何理由使用auto,但是是否有任何旧的源代码或类似的内容与引用的语句相对应?

\n

Chi*_*roh 61

作者回答: 我刚刚给范德林登先生发了电子邮件,他是这样说的:

是的,我同意在堆栈溢出上回答的人的观点。我不确定,因为我从未使用过语言 B,但对我来说,“auto”最终出现在 C 中似乎非常合理,因为它是在 B 中。

即使当我在 20 世纪 80 年代用 C 进行专业内核和编译器编程时,我也从未见过任何我记得使用“auto”的代码。

关键要点是 auto 关键字不会添加任何额外信息,因此是多余且不需要的。把它带入C是一个错误!

我还要求他解释一下他所说的编译器编写和符号表的含义。以下是他的回应:

假设您正在编写一个编译器,它将 C 源代码转换为链接器对象(可以链接的对象文件)。

每当您的词法分析器(编译器的前端)找到形成用户定义符号的字符序列(可能是变量,可能是函数名称,可能是常量等)时,编译器都会将该名称存储在表称为“符号表”。它还将存储它所知道的有关符号的所有其他内容 - 如果它是变量,它将存储其类型,如果是常量,它将存储值,如果是函数,它会注意到它可以被调用,等等。还存储名称的范围(已知该符号的代码行)。符号表是编译器的核心数据结构之一,其中一些被带入目标文件中。目标文件需要知道外部代码对象可寻址的任何名称,因此链接器可以将名称的使用与存储该名称的对象相关联。

然后,当编译器遇到相同的名称时,编译器会在符号表中查找,看看它是否已经知道有关该名称的所有信息。存储有关名称的有用项目之一是“编译器将为其分配存储的位置”。只要符号仍在范围内,就必须维护该存储。因此,符号表知道在运行时应该在哪里分配存储空间是很有用的。我给出了 3 个可能存储变量的不同位置的示例。“auto”关键字告诉编译器“这是一个变量,您应该将其存储在堆栈上,其作用域是声明它的函数”。

只是,编译器不需要被告知这一点,因为对于函数内声明的所有变量来说都是如此。我希望这个解释是有道理的。

我想我完全误解了他的说法,认为auto在用C编写编译器时,在处理符号表的代码中可能有一些用法,但似乎他的意思auto是没用的,但C编译器编写者必须处理它并理解它。但我还是要求他确认我的错误,这确实是我的误会:

也许思考这个问题的最佳方式是:

  1. “auto”在 C 中没有语义效果
  2. 我们认为它来自 B,但不确定。
  3. 它向编写 C 代码编译器的人传达信息。
  4. 但该信息与编译编写器拥有的其他信息重复。
  5. 因此编译器编写者可以记下任一信息来更新符号表
  6. 或者实际上,他们可以检查两条信息是否一致,如果不一致,则发出错误消息。

  • 还有更多内容。`auto` 关键字的存在是因为最早的 C 编译器没有它就无法编译。另一轮语法提升本来可以删除它,但它没有完成。仅使用正则表达式删除“auto”是行不通的,因为变量在编译器源代码本身中被声明为“auto x;”。你看,“int”是隐式的。 (7认同)
  • 我认为这个想法是编译器在符号表中维护_存储类别_(例如静态、堆栈等)。考虑到从源代码到编译器内部的映射很薄,关键字或多或少直接映射:“static”映射到静态,“auto”映射到堆栈,可能“register”映射到寄存器等(仅,auto是隐式的,寄存器是由编译器计算的,关键字被忽略)。 (3认同)
  • @Chi_Iroh:早早的。我们正在谈论最初的 PDP-11 Unix 编译器。这种奇怪的行为之所以存在,是因为编译器依赖于它。(您可以检查 pcc 并查看它是否仍然存在。)(AFAIK std=c89 可以成功编译所有 K&R C,因此不需要早期选项。) (2认同)

chq*_*lie 39

据我从 40 多年的 C 编程(包括编译器工作)来看,关键字auto在 C 中已经完全没用了 50 年。

为了回答你的确切问题,为什么auto关键字对于 C 语言的编译器编写者有用?它根本没有用;C 编译器编写者只需将其解析为关键字并将其语义实现为存储类说明符。

它似乎是C 语言的前身B语言的遗留物,由贝尔实验室的 Ken Thompson 和 Dennis Ritchie 在六十年代末七十年代初开发。我从未使用过 B,而且我怀疑1984 年我在 Inria 认识的Peter也使用过 B。

在C23之前,auto只能用于为函数范围内的定义指定自动存储类。这是默认值,因此auto完全多余,只要指定类型或另一个限定符,auto就可以删除。没有任何情况需要它,因此它包含在 C 标准中只是植根于 C 语言的早期历史。

auto自 C++11 起已在 C++ 中使用,以在变量定义中启用类型推断(无论是否带有自动存储),其中编译器会根据初始值设定项的类型检测类型。

随着当前趋势推动 C 和 C++ 语言公共子集的融合,在 C23 中,新的语义已附加到此关键字,该关键字以 C++ 语义为模型,但受到更多限制:

6.7.1 存储类说明符

auto可能会与所有其他人一起出现,除了typedef;

auto仅应出现在具有文件范围的标识符的声明说明符中,或者如果要从初始值设定项推断类型,则应与其他存储类说明符一起出现。

如果auto与另一个存储类说明符一起出现,或者如果它出现在文件范围的声明中,则在确定链接的存储持续时间时将忽略它。那么它仅表明可以推断所声明的类型。

类型推断指定为:

6.7.9 类型推断

约束条件

1 推断类型的声明应包含存储类说明符auto

描述

2 对于作为对象定义的声明,初始化声明符应具有以下形式之一

直接声明符 = 赋值表达式
直接声明符 = { 赋值表达式 }
直接声明符 = { 赋值表达式 , }

声明的类型是左值、数组到指针或函数到指针转换之后的赋值表达式的类型,另外由限定符限定并由出现在声明说明符中的属性(如果有)修改。如果直接声明符不是标识符 attribute-specifier-sequence opt形式(可能用一对平衡的括号括起来),则行为未定义。

类型推断在 C++ 中非常有用,因为类型可能非常复杂,几乎不可能在变量定义中指定,尤其是在模板中。相反,在 C 语言中使用它可能会适得其反,降低代码的可读性并鼓励懒惰和容易出错的做法。将指针隐藏在 typedef 后面已经够糟糕的了,现在你可以使用auto关键字完全隐藏它们。


最后,我记得在棘手的面试测试中使用过它,要求候选人找出这段代码无法编译的原因:

#include <stdio.h>
#include <string.h>

int main(void) {
    char word[80];
    int auto = 0;
    while (scanf("%79s", word) == 1) {
        if (!strcmp(word, "car")
        ||  !strcmp(word, "auto")
        ||  !strcmp(word, "automobile"))
            auto++;
    }
    printf("cars: %d\n", auto);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • @TedLyngmo:恕我直言,尝试将 C 集中到它的远亲是一个悲伤的举动。 (7认同)
  • @pmacfarlane 我们的工作场所刚刚建立了一个代码检查器,只要可以通过上下文推断类型,就坚持使用“auto”。我真的不喜欢这条规则,因为有时显式类型是关于您正在使用的内容的有用文档。 (6认同)
  • *在 C++ 中使用 auto 来启用类型推断* - 根据记录,这是 C++11 中的新增功能。在此之前的 C++ 中,“auto” 的工作方式与 C23 之前的 C 中一样,作为存储类说明符。https://godbolt.org/z/6WeGab6of(并推导出仅使用 C++14 的函数的返回类型。) (4认同)
  • @pmacfarlane 它并不完全具有 C++ 语义,但它可以用于推断类型,如 C23 中的“auto x = foo();”。 (3认同)
  • 难道 `auto` 具有 C++ 语义在 C23 中不是已经定下来了吗? (2认同)
  • @pmacfarlane:恐怕你是对的……C23 中有太多废话,我错过了这个。答案已修改 (2认同)
  • @chqrlie 它将产生大量新的 SO 问题,人们通过在任何地方使用“auto”来搞乱他们的类型。这是一个非常懒惰的解决方案,导致的问题多于它解决的问题。话虽如此,我喜欢 C++ 中的它,因为它有一些在 C 中没有的非常冗长的复合类型。 (2认同)

use*_*670 19

关键字auto起源于B语言,在B语言中它实际上非常有用,它允许编译器区分本地名称和非本地名称(用extrn关键字标记):

main()
{
    extrn printf;
    auto x;
    x = 25;
    printf('%d', x);
}
Run Code Online (Sandbox Code Playgroud)

当B语言演变成C语言时,它保留了高度的向后兼容性。在 B 中基本上只有一个“单元”类型,因此在 C 中他们引入了类型注释作为可选功能。在 C89 及更早版本中,auto已用于引入本地名称的相同目的:

main()
{
    extern printf();
    auto x; /* type is int by default */
    x = 42;
    printf("%d", x);
}
Run Code Online (Sandbox Code Playgroud)

在线编译器

在语言焦点转向强制类型安全之后,对auto说明符的需求完全消失了,因为类型注释的存在允许区分本地名称声明。


Mat*_* M. 12

首先是 4 或 5 个存储类说明符auto之一:、、、和从 C11 开始。C 中的每个变量都有一个来自上述列表的关联存储类说明符,如果未指定,则为默认值。autoregisterstaticextern_Thread_localauto

从用户的角度来看,由于auto是默认的,所以很少需要指定它,并且可以说这样做只是噪音 -如果通常不使用说明符,其他说明符会更加突出。

然而,从编译器编写者的角度来看,由于每个变量都有一个存储类说明符,因此的概念auto至关重要,并且将自己置于他们的位置,您可以想象某个地方存在enum枚举 4(或 5)个不同的说明符和每个变量附加枚举值之一的声明。

它出现在编译器中的事实并不要求它出现在语言中,但它确实为其提供了一个参数:正则性。无论是否直接暴露(或不直接暴露),这个概念都存在,并且暴露它的成本很小,所以也可以,不是吗?

1 @BenVoigt 提到它在宏中可能很有用,其中类型是用户提供的,因为它可以防止用户指定另一个存储说明符,例如static,因为编译器不会接受两个存储说明符。

  • 在一种情况下,它在当前版本的 C 程序 ( &gt; C90 &amp;&amp; &lt; C23 ) 中很有用——如果涉及宏的话。“MAILBOX x;”可能是“int”,可能是“char”,可能是“易失性”,可能是“静态”。`auto MAILBOX x;` 可能是 `int`,可能是 `char`,可能是 `volatile`.... 但可以肯定它不是 `static`,如果出现以下情况,您将得到一个编译错误,而不是默默地损坏代码任何未来的程序员都曾尝试将“static”添加到“MAILBOX”的#define 中。 (6认同)
  • 有趣的论点,但还有其他概念*规则性*需要更多关键字:全局符号的“static”与*public*,局部符号的“static”与*dynamic*,“extern”与*本地定义的*符号, `const` 与 *可修改*...拥有冗余关键字实际上违背了 C 设计者的基本价值观之一:简单性。他们很可能保留了“auto”关键字,以便与最初用 B 编写的古代代码兼容,如 user7860670 的答案所示,其中“int”是隐式的。 (3认同)