C++编译器 - 解析类成员的名称

3
当编译器看到这段代码时:
SomeClass foo;
int x = foo.bar;

在检索bar的值时,它是通过什么过程进行的?即,它是否查看表示类定义的某些数据结构?如果是这样,那么这个数据结构是在编译时还是运行时生成的?


2
请注意,以下所有答案(包括我的)都应被视为“通常情况下,在典型的编译器中,这是发生的事情”。与几乎所有关于C++实现的事情一样,真正的答案是,“完全取决于编译器编写者,只要程序运行时得出正确的答案,他们可以随心所欲地做”。在应用恶魔般的优化之前,下面描述的是最明显、最合理的方法。 - Steve Jessop
5个回答

6
编译器知道foo的地址,该地址有足够的空间存储成员变量(sizeof(SomeClass)),其中可能包括一些填充。
它知道`bar在类中的某个位置(通常是它们被声明的顺序,加上一些其他魔法,如继承),然后跳转到该偏移量。
也就是说:
struct SomeClass
{
    short s;
    float f;
    int bar;
    char *c;
}

// pseudo-code:
&SomeClass.bar == (&SomeClass) + sizeof(short) + sizeof(float);

运行时,它获取数据并将其分配给x


1
编译器会保存foo的地址 - 通常是相对于某个栈指针的偏移量,因为foo是一个自动变量。 - Steve Jessop
2
这在技术上是不准确的。编译器通常不会将foo.bar分配给x。编译器会生成机器指令,在运行时将foo.bar分配给x。 - JSBձոգչ
那就是我想表达的意思...我想我会在运行时添加,尽管我认为那应该是暗示的。 - GManNickG
由于SomeClass是POD,因此“&foo.bar == &foo + offsetof(SomeClass, bar)”也可以工作。@OP:请注意,上述是一个简化的情况。当您涉及多态类(带有其虚拟表)和优化编译器来处理类布局时,它会变得更加复杂。请参见:https://dev59.com/HXNA5IYBdhLWcg3wAI3d - outis
@GMan:通常在编译时,编译器只知道偏移量。通常操作系统会在运行时提供堆栈的实际地址。显然,存在一些体系结构,其中堆栈映射到固定的虚拟(甚至物理)地址,因此编译器可以知道实际地址。但我认为这不应该被视为常态。 - Steve Jessop
显示剩余2条评论

4
在编译时,编译器会有一些数据结构,告诉它如何访问SomeClass的每个成员。对于简单情况,它只是一个偏移量,但如果你有非平凡的继承,可能会更复杂。
为了处理您的表达式,编译器查阅这个内部数据,并(最终)发出适当的机器代码。到运行时,这个结构将被丢弃,剩下的只是从foo地址开始执行所需操作的代码。但是,如果你有一个指向bar的成员指针,那么如何访问bar成员的细节以某种方式封装在该指针值中(可能是一个偏移量,也可能更复杂)。

2
这个过程始于编译器发现 SomeClass 的定义。基于该定义,它构建了一个内部结构,其中包含 SomeClass 中字段的类型以及方法代码的位置。
当你写下 SomeClass foo; 时,编译器会找到对应于 SomeClass构造函数 的代码,并创建调用该代码的机器指令。在下一行中,你写下 int x = foo.bar。这里编译器会写出机器指令来为 int 分配堆栈空间,然后查看其 SomeClass 数据结构。该数据结构将告诉它从 foo 对象开始的 bar 的字节偏移量。编译器然后会写出机器码将与 bar 相应的字节复制到 x 的内存中。所有这些机器码都被写入你的可执行文件中。
通常,在编译完成后,表示 SomeClass 和其他定义的数据结构都会被丢弃。剩下的只是一组机器指令。当你实际运行程序时,这些指令会被执行,因此构造函数和将 foo.bar 复制到 x 的代码会由 CPU 执行,而不需要显式了解对象的结构。
这是一般情况。在使用调试器和优化时有特殊情况,但这通常是发生的事情。

1
你必须明白,编译时每个类都会被转换成一个结构体(为了简化解释),所以如果你有。
class Foo
{
   int x, y, z;
   char bar[10];
   ... etc ...
}

它们被转换成一个具有指定大小的结构体,这里是4*3 + 10字节。然后,根据对齐方式将它们更方便地排列,记住例如在偏移量4处可以找到属性y,在地址8处可以找到z。

然后很容易,只需将参与赋值的类的地址加上4,就可以得到y等的地址。


2
在你的代码中,使用sizeof(int)来代替数字4。因为int类型的字节数并不一定是4个字节。 - GManNickG

1
编译器只在编译时存储类元数据。你的第一个问题,它如何检索bar的值,实际上相当复杂。你可以将其视为从对象foo计算bar的偏移量,然后读取该位置的内存。但是,根据x的实际使用方式,它可能会执行完全不同的操作。在某些情况下,“x”可能根本不出现在编译代码中。

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