提问人:Chi_Iroh 提问时间:6/22/2023 最后编辑:Chi_Iroh 更新时间:7/22/2023 访问量:5516
为什么“auto”关键字对 C 语言中的编译器编写器有用?
Why is the 'auto' keyword useful for compiler writers in C?
问:
我目前正在阅读“Expert C Programming - Deep C Secrets”,刚刚遇到这个:
从不需要存储类说明符。它对编译器编写者来说最有意义 在符号表中输入一个条目 — 它说“此存储是在输入 块“(而不是在编译时静态分配,或在堆上动态分配)。 对于所有其他程序员来说几乎毫无意义,因为它只能在函数内部使用,但是 默认情况下,函数中的数据声明具有此属性。
auto
auto
我看到有人在这里问了同样的事情,但他们没有任何答案,评论中给出的链接只解释了为什么 C 中有这样一个关键字,继承自 B,以及与 C++11 或 C++11 之前的区别。
无论如何,我发帖是为了专注于说明关键字在编译器编写中以某种方式有用的部分,但是这个想法或与符号表的联系是什么?auto
我真的坚持这样一个事实,即在用 C 语言编写编译器时,我只询问潜在的用法(而不是编写 C 编译器)。
为了澄清一下,我之所以问这个问题,是因为我想知道是否有代码示例是合理的,因为作者说在编写编译器时会有。auto
这里的重点是我认为已经理解了(继承自 B,它是强制性的,但在 C 中毫无用处),但我无法想象任何使用它是有用的(或者至少不是无用的)的例子。auto
似乎真的没有任何理由使用 ,但是是否有任何旧的源代码或类似的东西与引用的语句相对应?auto
答:
据我所知,从 40+ 年的 C 编程,包括编译器工作,这个关键字在 C 中已经完全没用了 50 年。auto
为了回答您的确切问题,为什么 auto
关键字对 C 语言中的编译器编写器有用?它根本没有用;C 编译器编写器只需要将其解析为关键字,并将其语义实现为存储类说明符。
它似乎是 B 语言的遗留物,B 是 C 语言的前身,由 Ken Thompson 和 Dennis Ritchie 在 60 年代末和 70 年代初在贝尔实验室开发。我从未使用过 B,我怀疑 1984 年在 Inria 遇到的 Peter 也使用过。
在 C23 之前,只能用于为函数范围内的定义指定自动存储类。这是默认设置,因此是完全冗余的,只要指定了类型或其他限定符,就可以删除。没有任何需要它的情况,因此将其包含在 C 标准中仅植根于 C 语言的早期历史。auto
auto
auto
auto
自 C++ 11 以来,一直在 C++ 中用于在变量定义中启用类型推断,无论是否具有自动存储,其中编译器从初始值设定项的类型中检测类型。
随着当前趋势推动 C 和 C++ 语言的通用子集的收敛,在 C23 中,以 C++ 语义为模型的这个关键字附加了新的语义,但受到更多限制:
6.7.1 存储类说明符
auto
可能与所有其他产品一起出现,但typedef
;
auto
仅应出现在具有文件范围的标识符的声明说明符中,或者如果要从初始值设定项推断类型,则应与其他存储类说明符一起出现。如果与另一个存储类说明符一起出现,或者如果它出现在文件作用域的声明中,则在确定链接的存储持续时间时将忽略它。然后,它仅指示可以推断声明的类型。
auto
类型推断指定为:
6.7.9 类型推断
约束
1 推断其类型的声明应包含存储类说明符 。
auto
描述
2 对于作为对象定义的这种声明,初始化声明者应具有以下形式之一
direct-declarator = 赋值表达式 direct-declarator = { 赋值表达式 } direct-declarator = { 赋值表达式
, }声明的类型是左值、数组到指针或函数到指针转换之后的赋值表达式的类型,另外由限定符限定,并由声明说明符中出现的属性(如果有)进行修改。如果直接声明符不是表单标识符 attribute-specifier-sequenceopt(可能用平衡的括号对括起来),则行为未定义。
类型推断在 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;
}
评论
auto
auto x = foo();
auto
auto
该关键字源自 B 语言,它实际上非常有用,并允许编译器将本地名称与非本地名称(用关键字标记):auto
extrn
main()
{
extrn printf;
auto x;
x = 25;
printf('%d', x);
}
当 B 语言演变为 C 语言时,它保留了高度的向后兼容性。在 B 中,基本上只有一个“单元格”类型,因此在 C 中,他们引入了类型注释作为可选功能。在 C89 及之前,用于引入本地名称的相同目的:auto
main()
{
extern printf();
auto x; /* type is int by default */
x = 42;
printf("%d", x);
}
在语言重点转向强制类型安全之后,对说明符的需求完全消失了,因为类型注释的存在允许区分本地名称声明。auto
作者回答:我刚刚给Van der Linden先生发了电子邮件,他是这样说的:
是的,我同意在堆栈溢出上回答的人。 我不确定,因为我从未使用过 B 语言,但在我看来,“auto”最终出现在 C 中是很有道理的,因为它在 B 中。
即使在 1980 年代我专业地使用 C 语言进行内核和编译器编程时,我也从未见过任何我记得使用“auto”的代码。
关键的一点是,auto 关键字不会添加任何额外的信息,因此是多余的和不必要的。把它带入C是一个错误!
我还要求解释一下他所说的编译器编写和符号表是什么意思。以下是他的回应:
假设您正在编写一个编译器,该编译器将 C 源代码转换为链接器对象(可以链接的目标文件)。
每当词法分析器(编译器的前端)找到构成用户定义符号的字符序列(可能是变量、函数名称、常量等)时,编译器就会将该名称存储在称为“符号表”的表中。它还将存储它所知道的关于符号的所有其他信息 - 如果它是一个变量,它将存储它的类型,如果是一个常量,它将存储值,如果是一个函数,它将注意到它可以被调用,等等。它还将存储名称的范围(已知此符号的代码行)。符号表是编译器的核心数据结构之一,其中一些被传递到目标文件中。对象文件需要知道外部代码对象可寻址的任何名称,以便链接器可以将名称的使用与存储该名称的对象相关联。
然后,当编译器遇到相同的名称时,编译器会在符号表中查看它是否已经知道该名称的所有信息。存储有关名称的有用项目之一是“编译器将为其分配存储的位置”。只要符号保持在范围内,就必须维护该存储。因此,符号表知道在运行时应该在哪里分配存储是很有用的。我举了 3 个例子,说明变量可能被存储的不同位置。“auto”关键字告诉编译器“这是一个变量,您应该将其存储在堆栈上,其作用域是声明它的函数”。
只是,编译器不需要被告知这一点,因为对于函数中声明的所有变量来说,这已经是正确的。 我希望这个解释是有道理的。
我想我完全误解了他的陈述,认为在用 C 编写编译器时,在处理符号表的代码中可能有一些用法,但似乎他的意思是没用的,但 C 编译器编写者必须处理它并理解它。
尽管如此,我还是请他确认我的错误,这确实是我的误解:auto
auto
也许最好的思考方式是:
- “auto”在 C 中没有语义效果
- 我们认为它来自 B,但不确定。
- 它将信息传达给为 C 代码编写编译器的人。
- 但该信息是编译编写器拥有的其他信息的副本。
- 因此,编译器编写者可以记下任何一条信息来更新符号表
- 或者,他们可以检查两条信息是否一致,如果不是,则发出错误消息。
评论
static
auto
register
auto
auto
auto x;
int
首先是 4 或 5 个存储类说明符之一:、、、、和 C11 开始。C 中的每个变量都有一个来自上述列表中的关联存储类说明符,如果未指定,则为默认值。auto
auto
register
static
extern
_Thread_local
auto
从用户的角度来看,由于是默认值,因此很少需要指定它,可以说这样做只是噪音 - 如果通常不使用说明符,其他说明符会更加突出。auto
然而,从编译器编写者的角度来看,由于每个变量都有一个存储类说明符,因此这个概念是最重要的,设身处地为他们着想,你可以想象某个地方存在一个枚举 4 个(或 5 个)不同的说明符,每个变量声明都附加了一个枚举值。auto
enum
它出现在编译器中这一事实并不要求它出现在语言中,但它确实为它提供了一个参数:规律性。无论它是否直接暴露(或是否直接暴露),这个概念都存在,并且暴露它的成本很小,所以也可能,不是吗?
1 @BenVoigt提到,在类型由用户提供的宏中,它可能很有用,因为它可以防止用户指定另一个存储说明符,例如 static
,因为编译器不会接受两个存储说明符。
评论
static
static
extern
const
auto
int
register
static
extern
static
auto
register
const
volatile
MAILBOX x;
int
char
volatile
static
auto MAILBOX x;
int
char
volatile
static
static
MAILBOX
C 语言中的 auto 关键字对大多数程序员来说不是很有用。但是,它对编译器编写器很有用。
符号表是一种数据结构,编译器使用它来跟踪程序中的所有变量和函数。当编译器看到自动声明时,它知道变量将在堆栈上分配。这意味着编译器可以优化该变量的代码,例如避免将其存储在寄存器中。
例如,请考虑以下函数:
void soso(int x) {
int y = x * 2;
// The compiler could optimize this code if it knew that y was allocated on the stack.
int z = y + 3;
}
如果编译器知道 y 是在堆栈上分配的,则可以避免将 y 存储在寄存器中。这将节省内存并提高函数的性能。
当然,auto 关键字并不总是提高编译器生成代码性能所必需的。但是,对于想要优化代码的编译器编写者来说,它可能是一个有用的工具。
以下是有关 auto 关键字的一些其他详细信息:
auto 关键字在 C 中不是必需的。编译器将自动假定在函数中声明的任何变量都已在堆栈上分配。 auto 关键字可用于在函数外部声明变量。但是,不建议这样做,因为它会使代码更难阅读和理解。 auto 关键字并非在所有 C 编译器中都可用。某些编译器可能仅在某些情况下支持它。
评论
auto
auto