C++代码的符号表是否包含函数名称和类名称?

6
我一直在搜寻关于C++代码符号表是否包含函数名和类名的各种帖子。我发现一个帖子提到,这取决于编译器类型,如果是单遍编译器,则不需要将类名和子程序名称存储在符号表中。但是,如果是多遍编译器,它可能会添加关于所遇到的类及其子程序的信息,以便进行参数类型检查并发出有意义的错误消息。 我无法理解它是否实际上与编译器相关?我一直认为(对于C++代码),编译器会将函数名与类名放在表中,无论是单遍还是多遍编译器。它如何依赖于遍数?我没有这样深入的知识。此外,有人能展示一个简单C++类的符号表示例吗?它会是什么样子(包含类名的函数名)?

我们在谈论符号表,它必须明确无误地标识实体以解决外部依赖关系。你认为如果不包括函数名,"明确无误地"怎么能起作用呢? - DevSolar
2
阅读此链接:https://en.m.wikipedia.org/wiki/Name_mangling - Alex Lop.
1
如果代码可以一次编译通过,那么它就不需要在符号表中存储类名和子程序名。我无法想象你的意思是什么。你认为符号表是用来做什么的?我认为你有一些基本的误解,这个问题的根源在于此。 - davmac
基本上符号表包含完全限定名称,包括参数类型。 - skyking
请注意,任何带有枚举列的数据库表都可以拆分成多个表。如果我有一个名为 FOO 的表,其中包含一个 COLOR=RED | GREEN | BLUE 列,我可以将该表拆分成三个表 FOO_REDFOO_GREENFOO_BLUE。我可以将一个带有 TYPE = FUNCTION | CLASS | VARIABLE 的符号表拆分成 3 个单独的函数、类和变量表。从逻辑上讲,它们都是相同的,只是方便的问题。 - MSalters
3个回答

8
大多数编译器教材都会介绍符号表,并经常展示像Pascal这样的适度复杂语言的详细信息。你不会在教材中找到有关C++符号表的信息;它太过神秘。
我们为DMS软件重构工具包提供完整的C++14前端。它解析C++,构建详细的ASTs,并执行名称和类型解析,其中包括构建精确的符号表。
接下来是我们关于如何使用DMS的教程中关注C++符号表结构的幻灯片。

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

请注意,类名被记录在(唯一的)全局命名空间中,因为它们是在顶层声明的。如果它们在某个明确的命名空间中定义,那么该命名空间将具有自己的相应符号空间,记录已声明的类,并且该命名空间本身将记录在全局符号空间中。

C++ Symbol Table: Class Perspective

OP并没有询问函数体的符号表长什么样子,但我碰巧有一张说明性幻灯片,如下所示。 符号空间的工作方式相同。 此幻灯片显示了符号空间与其表示的作用域区域之间的链接。 该链接实际上是由与符号空间相关联的指针实现的,指向相应的AST(命名空间定义可以分散在多个位置)。请注意,在这种情况下,函数名称记录在全局命名空间中,因为它在顶层声明。 如果它在类的范围内定义,则函数名称将记录在类体的符号空间中(在前面的图表中)。

C++ Symbol Table: Function Perspective

作为一项普遍规则,符号表的组织方式完全取决于编译器和设计者的选择。在我们的情况下,我们设计了一个非常通用的符号表管理包,因为我们计划(并且已经)使用相同的包以统一的方式处理多种语言(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的符号表。


什么是“精确符号表”?我所知道的所有链接器都能够使用默认的“精确”符号表进行工作...那么究竟有什么区别呢? - Klaus
有很多粗糙的逆向工程工具,它们构建的符号表不完整,甚至无法正确地模拟继承和重载查找等。我认为Doxygen现在使用Clang,但早期版本使用了一种完全(粗糙)的解析C++的方法,然后构建了这样一个粗糙的表。实际操作真正程序的工具(如GCC、Clang、链接器、DMS)不能避免精度,否则它们将无法正常工作。 - Ira Baxter

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)

可能会出现 af 符号不会存储在顶层符号表中的情况,而是每个命名空间(包括C++命名空间和类)都有自己的符号表,其中包含定义在其中的符号。但这只是一种数据结构选择。您仍然可以将符号表抽象地视为一个平面表,在其中名称映射到构造。
通常,“A::a” 符号不会输出到目标文件中,因为它对于链接不是必需的。

0

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

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

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

它使用了--demangle标志来查看原始名称。您可以编译随机的短程序来查看输出结果。


3
混淆名称通常不包括返回值类型。 - jxh
你正在混淆嵌入在目标文件中的符号和编译器构建的符号表,而编译器本身对于重载函数名没有任何用处。 - Ira Baxter

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