我一直在搜索有关C ++代码的符号表是否包含函数名称和类名的各种帖子。我可以在帖子上找到的东西是它取决于编译器的类型,
如果它在一遍中编译代码,则不需要在符号表中存储类名和子例程名
但如果它是一个多遍编译器,它可以添加有关它遇到的类及其子例程的信息,以便它可以进行参数类型检查并发出有意义的错误消息。
我无法理解它是否真的依赖于编译器?我假设编译器(对于C ++代码)会在表中放置带有类名的函数名,无论它是单通道还是多通道编译器。它如何依赖传球?我没有这么棒的知识。 此外,任何人都可以显示一个简单的C ++类的示例符号表,它会是什么样的(带有类名的函数名)?
答案 0 :(得分:7)
大多数编译器教科书都会告诉您有关符号表的信息,并经常向您展示有关适度复杂语言的详细信息,例如Pascal。您无法在教科书中找到有关C ++符号表的信息;这太神秘了。
我们为DMS Software Reengineering Toolkit提供完整的C ++ 14前端。它解析C ++,构建detailed ASTs,并执行名称和类型解析,包括构建精确的符号表。
以下是我们的教程中有关如何使用DMS的幻灯片,主要关注C ++符号表结构。
OP专门询问了关于课程会发生什么的看法。下图显示了左上角的小型C ++程序。图的其余部分显示了框,代表我们称之为"符号空间" (或"范围"),它们实质上是哈希表,映射符号名称(每个框列出它拥有的符号)到DMS知道该符号的信息(源文件定义位置,AST节点列表,引用定义,以及表示类型的复杂联合,并且可能反过来指向其他类型)。箭头表示符号空间是如何连接的;从空间A到空间B的箭头表示"范围A包含在范围B"中。通常,符号空间查找过程,搜索范围A代表符号x,如果在A中未找到x,则将继续在范围B中搜索。您将注意箭头用整数编号;这会告诉搜索机器首先查看编号最小的父作用域,然后尝试使用较大数字的箭头搜索作用域。这是范围的排序方式(注意C类继承自A和B; C类中字段的任何查找,例如" b"将被强制首先查看A的范围,然后在B的范围。通过这种方式,可以实现C ++查找规则。请注意,类名称记录在(唯一)全局名称空间中,因为它们是在顶级声明的。如果它们已在某个显式命名空间中定义,则命名空间将具有其自己的相应符号空间,用于记录声明的类,并且命名空间本身将记录在全局符号空间中。
OP没有询问函数体的符号表是什么样的,但我恰好有一个说明性的幻灯片,下面也是如此。 符号空间的工作方式相同。此幻灯片中显示的是符号空间与其表示的范围区域之间的链接。该链接实际上是通过与符号空间相关联的指针实现的,相应的AST(s,命名空间定义可以分散在多个位置)。请注意,在这种情况下,函数名称记录在全局名称空间中,因为它是在顶级声明的。如果它已在类的范围内定义,则函数名称将记录在类主体的符号空间中(在上图中)。
作为一般规则,符号表组织方式的详细信息完全取决于编译器以及设计者的选择。在我们的例子中,我们设计了一个非常通用的符号表管理包,因为我们计划(并且已经)使用相同的包以统一的方式处理多种语言(C,C ++,Java,COBOL,几种遗留语言)。 但是,符号空间和继承的抽象结构必须在C ++编译器中以基本相同的方式实现;毕竟,他们必须对相同的信息进行建模。我期待GCC和Clang编译器中的类似结构(嗯,整数编号的继承弧,可能不是:)
作为一个实际问题,并没有多少"通过"你的编译器有。它几乎必须构建这些结构以记住它对符号的了解,在传递中以及传递。
虽然building a C++ parser is very hard by itself,但构建这样的符号表要困难得多。这项工作使构建C ++解析器的工作相形见绌。我们的C ++名称解析器是由DMS编译和执行的250K SLOC属性 - 语法代码。获得细节权利是一个巨大的麻烦; C ++参考手册是庞大的,令人困惑的,事实遍布文档的各个地方,并且在各种各样的地方它是矛盾的(我们试图向委员会发送关于此的投诉)和/或编译器之间的不一致(我们有GCC的版本)和Visual Studio 201x)。
2017年3月更新:现在有C ++ 2014的符号表。 2018年6月更新:现在有C ++ 2017的符号表。
答案 1 :(得分: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 ++名称空间和类)都有自己的符号table,包含在其中定义的符号。但可以说,这只是一种数据结构选择。您仍然可以将符号表抽象地视为平面表,其中名称映射到构造。
通常,“A :: a”符号不会输出到目标文件,因为链接不需要它。