6

我一直在搜索有关 C++ 代码的符号表是否包含函数名称和类名称的各种帖子。我可以在帖子上找到的东西是它取决于编译器的类型,

如果它一次性编译代码,则不需要在符号表中存储类名和子例程名

但如果它是一个多遍编译器,它可以添加有关它遇到的类及其子例程的信息,以便它可以进行参数类型检查并发出有意义的错误消息。

我不明白它是否真的依赖于编译器?我假设编译器(用于 C++ 代码)会将函数名和类名放在表中,无论它是单通道编译器还是多通道编译器。它如何依赖于通行证?我没有这么好的/深入的知识。此外,任何人都可以显示一个简单的 C++ 类的示例符号表,它看起来如何(带有类名的函数名)?

4

3 回答 3

8

大多数编译器教科书都会告诉你符号表,并经常向你展示关于诸如 Pascal 之类的适度复杂性语言的详细信息。您不会在教科书中找到有关 C++ 符号表的信息。这太神秘了。

我们为我们的 DMS 软件再造工具包提供完整的 C++14 前端。它解析 C++,构建详细的 AST,并执行名称和类型解析,其中包括构建精确的符号表。

以下是我们教程中关于如何使用 DMS 的幻灯片,重点介绍了 C++ 符号表结构。

OP 专门要求了解类发生了什么。下图显示了左上角的微型 C++ 程序的情况。该图的其余部分显示了框,它们表示我们所谓的“符号空间”(或“范围”),它们本质上是哈希表,将符号名称(每个框列出它拥有的符号)映射到 DMS 知道的关于该符号的信息(定义的源文件位置,引用定义的 AST 节点列表,以及表示类型的复杂联合,并且可能反过来指向其他类型)。箭头显示符号空间是如何连接的;从空间 A 到空间 B 的箭头表示“范围 A 包含在范围 B 内”。通常是符号空间查找过程,在范围 A 中搜索符号 x,如果在 A 中找不到 x,将继续在范围 B 中搜索。您会注意到箭头用整数编号;这告诉搜索机制在尝试使用较大数字的箭头搜索范围之前首先查看编号最小的父范围。这就是作用域的排序方式(注意 C 类从 A 和 B 继承;对 C 类中的字段(例如“b”)的任何查找都将被迫首先在 A 的作用域中查找,然后在 B 的作用域中查找。这样就实现了C++查找规则。

请注意,类名记录在(唯一的)全局命名空间中,因为它们是在顶层声明的。如果它们是在某个显式命名空间中定义的,那么命名空间就会有它自己的相应符号空间来记录所声明的类,而命名空间本身将被记录在全局符号空间中。

C++ 符号表:类透视图

OP 没有询问函数体的符号表是什么样的,但我恰好在下面也有一张说明性的幻灯片。符号空间的工作方式相同。这张幻灯片中显示的是符号空间和它所代表的范围区域之间的联系。该链接实际上是由与符号空间相关联的指针实现的,指向相应的 AST(命名空间定义可以分散在多个地方)。

请注意,在这种情况下,函数名称记录在全局命名空间中,因为它是在顶层声明的。如果它是在类的范围内定义的,则函数名称将记录在类主体的符号空间中(在上图中)。

C++ 符号表:函数视角

作为一般规则,如何组织符号表的细节完全取决于编译器和设计者所做的选择。在我们的案例中,我们设计了一个非常通用的符号表管理包,因为我们计划(并且已经)使用同一个包以统一的方式处理多种语言(C、C++、Java、COBOL、几种遗留语言)。然而,符号空间和继承的抽象结构必须在 C++ 编译器中以基本相同的方式实现;毕竟,他们必须对相同的信息进行建模。我希望 GCC 和 Clang 编译器中有类似的结构(好吧,整数继承弧,也许不是:)

实际上,您的编译器有多少“通过”并不重要。它几乎必须构建这些结构来记住它对符号的了解,一个通道内和跨通道。

虽然构建 C++ 解析器本身非常困难,但构建这样的符号表要困难得多。这种努力使构建 C++ 解析器的努力相形见绌。我们的 C++ 名称解析器是由 DMS 编译和执行的大约 250K SLOC 的属性语法代码。获得细节权利是一件非常头疼的事情。C++ 参考手册非常庞大,令人困惑,事实分散在文档中的任何地方,并且在很多地方它是矛盾的(我们试图向委员会发送关于此的投诉)和/或编译器之间的不一致(我们有 GCC 的版本和 Visual Studio 201x)。

2017 年 3 月更新:现在有 C++2014 的符号表。2018 年 6 月更新:现在有 C++2017 的符号表。

于 2015-08-14T14:43:47.723 回答
2

符号表将名称映射到程序中的结构。因此,它用于记录类、函数、变量以及程序中具有用户指定名称的任何其他内容的名称。

(有两种常见的符号表——一种是编译器在编译程序时维护的,另一种存在于目标文件中,以便可以链接到其他对象。两者密切相关,但不必相似内部表示。通常只有编译器符号表中的一些符号会输出到对象中)。

你说的部分内容没有意义:

如果它一次性编译代码,则不需要在符号表中存储类名和子例程名

如果无法在符号表中查找名称,编译器如何确定名称所指的构造?

但如果它是一个多遍编译器,它可以添加有关它遇到的类及其子例程的信息,以便它可以进行参数类型检查并发出有意义的错误消息。

它没有理由不能一次完成。

我不明白它是否真的依赖于编译器?

所有编译器都将使用符号表,但它的使用将隐藏在实现中。

我假设编译器(用于 C++ 代码)会将函数名和类名放在表中,无论它是单通道编译器还是多通道编译器。它如何依赖于通行证?

通行证如何依赖所有名称都在符号表中——这就是它的用途——通常符号解析对于编译器所做的一切都很重要,所以它需要尽早完成(即在第一遍中——实际上是多遍编译器编译器的第一次传递很可能只是为了构建符号表!)。

此外,任何人都可以显示一个简单的 C++ 类的示例符号表,它看起来如何(带有类名的函数名)?

我会给它一个刺:

class A
{
    int a;
    void f(int, int);
};

将产生一个包含符号“A”、“a”和“f”的符号表。通常,“a”和“f”将标有范围以简化查找,例如:

"A"  -> (class)
"A::a"  ->  (class variable member)
"A::f(int,int)"  ->  (class function member)

a和符号也可能f不会存储在顶级符号表中,而是每个名称空间(包括 C++ 命名空间和类)都有自己的符号表,其中包含定义在其中的符号。但这可以说只是数据结构的选择。您仍然可以将符号表抽象地视为一个平面表,其中名称映射到一个构造。

一般来说,“A::a”符号不会输出到目标文件,因为链接不需要它。

于 2015-08-14T14:15:20.260 回答
0

简短回答:是的,在 Linux 上使用“nm --demangle”

长答案:符号表中的函数包含函数名加上返回值,如果它属于一个类,则还有类名。但是名称,类型(并非总是)和类不是用它的全名写的,以使用更少的空间。这种字符串称为 demangle。但是您知道这个短名称是唯一的,您可以从中解析出完整的类名。要查看程序的符号表,您可以在 linux 上使用“nm”。

http://linux.about.com/library/cmd/blcmdl1_nm.htm

它获得了 --demangle 标志来查看原始名称。你可以编译随机的短程序来看看结果。

于 2015-08-14T14:19:36.873 回答