编译后的 C++ 类是什么样的?
有了汇编指令和 C 程序的一些背景知识,我可以想象编译函数的样子,但有趣的是,我从来没有仔细考虑过编译后的 C++ 类的样子.
With some background in assemble instructions and C programs, I can visualize how a compiled function would look like, but it's funny I have never so carefully thought about how a compiled C++ class would look like.
bash$ cat class.cpp
#include<iostream>
class Base
{
int i;
float f;
};
bash$ g++ -c class.cpp
我跑了:
bash$objdump -d class.o
bash$readelf -a class.o
但我得到的东西我很难理解.
but what I get is hard for me to understand.
谁能给我解释一下或建议一些好的起点.
Could somebody please explain me or suggest some good starting points.
推荐答案
类(或多或少)构造为常规结构.这些方法(或多或少......)转换为第一个参数是this"的函数.对类变量的引用是作为this"的偏移量完成的.
The classes are (more or less) constructed as regular structs. The methods are (more or less...) converted into functions which first parameter is "this". References to the class variables are done as an offset to "this".
至于继承,让我们引用 C++ FAQ LITE,这里反映了 http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.4 .本章展示了如何在真实硬件中调用虚函数(编译在机器码中做了什么.
As far as inheritance, lets quote from the C++ FAQ LITE, which is mirrored here http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.4 . This chapter shows how Virtual functions are called in the real hardware (what does the compile make in machine code.
让我们举个例子.假设 Base 类有 5 个虚函数:virt0()
到 virt4()
.
Let's work an example. Suppose class Base has 5 virtual functions: virt0()
through virt4()
.
// Your original C++ source code
class Base {
public:
virtual arbitrary_return_type virt0(...arbitrary params...);
virtual arbitrary_return_type virt1(...arbitrary params...);
virtual arbitrary_return_type virt2(...arbitrary params...);
virtual arbitrary_return_type virt3(...arbitrary params...);
virtual arbitrary_return_type virt4(...arbitrary params...);
...
};
第 1 步:编译器构建一个包含 5 个函数指针的静态表,将该表埋入静态内存中的某个位置.许多(不是全部)编译器在编译定义 Base 的第一个非内联虚函数的 .cpp 时定义此表.我们称该表为 v-table;让我们假设它的技术名称是 Base::__vtable
.如果一个函数指针适合目标硬件平台上的一个机器字,Base::__vtable
最终将消耗 5 个隐藏字的内存.不是每个实例 5 个,不是每个函数 5 个;只是 5. 它可能看起来像下面的伪代码:
Step #1: the compiler builds a static table containing 5 function-pointers, burying that table into static memory somewhere. Many (not all) compilers define this table while compiling the .cpp that defines Base's first non-inline virtual function. We call that table the v-table; let's pretend its technical name is Base::__vtable
. If a function pointer fits into one machine word on the target hardware platform, Base::__vtable
will end up consuming 5 hidden words of memory. Not 5 per instance, not 5 per function; just 5. It might look something like the following pseudo-code:
// Pseudo-code (not C++, not C) for a static table defined within file Base.cpp
// Pretend FunctionPtr is a generic pointer to a generic member function
// (Remember: this is pseudo-code, not C++ code)
FunctionPtr Base::__vtable[5] = {
&Base::virt0, &Base::virt1, &Base::virt2, &Base::virt3, &Base::virt4
};
第 2 步:编译器向 Base 类的每个对象添加一个隐藏指针(通常也是一个机器字).这称为 v 指针.把这个隐藏的指针想象成一个隐藏的数据成员,就好像编译器把你的类重写成这样:
Step #2: the compiler adds a hidden pointer (typically also a machine-word) to each object of class Base. This is called the v-pointer. Think of this hidden pointer as a hidden data member, as if the compiler rewrites your class to something like this:
// Your original C++ source code
class Base {
public:
...
FunctionPtr* __vptr; ← supplied by the compiler, hidden from the programmer
...
};
步骤#3:编译器在每个构造函数中初始化this->__vptr
.这个想法是让每个对象的 v-pointer 指向其类的 v-table,就好像它在每个构造函数的 init-list 中添加了以下指令:
Step #3: the compiler initializes this->__vptr
within each constructor. The idea is to cause each object's v-pointer to point at its class's v-table, as if it adds the following instruction in each constructor's init-list:
Base::Base(...arbitrary params...)
: __vptr(&Base::__vtable[0]) ← supplied by the compiler, hidden from the programmer
...
{
...
}
现在让我们创建一个派生类.假设您的 C++ 代码定义了继承自类 Base 的类 Der.编译器重复步骤#1 和#3(但不是#2).在第 1 步中,编译器创建一个隐藏的 v-table,保留与 Base::__vtable
中相同的函数指针,但替换那些对应于覆盖的槽.例如,如果 Der 通过 virt2()
覆盖 virt0()
并按原样继承其他的,则 Der 的 v-table 可能看起来像这样(假设 Der 没有添加任何新的虚拟):
Now let's work out a derived class. Suppose your C++ code defines class Der that inherits from class Base. The compiler repeats steps #1 and #3 (but not #2). In step #1, the compiler creates a hidden v-table, keeping the same function-pointers as in Base::__vtable
but replacing those slots that correspond to overrides. For instance, if Der overrides virt0()
through virt2()
and inherits the others as-is, Der's v-table might look something like this (pretend Der doesn't add any new virtuals):
// Pseudo-code (not C++, not C) for a static table defined within file Der.cpp
// Pretend FunctionPtr is a generic pointer to a generic member function
// (Remember: this is pseudo-code, not C++ code)
FunctionPtr Der::__vtable[5] = {
&Der::virt0, &Der::virt1, &Der::virt2, &Base::virt3, &Base::virt4
}; ^^^^----------^^^^---inherited as-is
在第 3 步中,编译器在 Der 的每个构造函数的开头添加了一个类似的指针赋值.这个想法是改变每个 Der 对象的 v 指针,使其指向其类的 v 表.(这不是第二个 v 指针;它与基类 Base 中定义的 v 指针相同;请记住,编译器不会在 Der 类中重复步骤 #2.)
In step #3, the compiler adds a similar pointer-assignment at the beginning of each of Der's constructors. The idea is to change each Der object's v-pointer so it points at its class's v-table. (This is not a second v-pointer; it's the same v-pointer that was defined in the base class, Base; remember, the compiler does not repeat step #2 in class Der.)
最后,让我们看看编译器如何实现对虚函数的调用.您的代码可能如下所示:
Finally, let's see how the compiler implements a call to a virtual function. Your code might look like this:
// Your original C++ code
void mycode(Base* p)
{
p->virt3();
}
编译器不知道这是要调用 Base::virt3()
还是 Der::virt3()
或者可能是 virt3()
另一个派生类的方法,它甚至还不存在.它只确定您正在调用 virt3()
,而这恰好是 v-table 的插槽 #3 中的函数.它将调用重写为如下内容:
The compiler has no idea whether this is going to call Base::virt3()
or Der::virt3()
or perhaps the virt3()
method of another derived class that doesn't even exist yet. It only knows for sure that you are calling virt3()
which happens to be the function in slot #3 of the v-table. It rewrites that call into something like this:
// Pseudo-code that the compiler generates from your C++
void mycode(Base* p)
{
p->__vptr[3](p);
}
<小时>
我强烈建议每位 C++ 开发人员阅读常见问题解答.这可能需要几个星期(因为它很难阅读而且很长),但它会教你很多关于 C++ 的知识以及可以用它做什么.
I strongly recommend every C++ developer to read the FAQ. It might take several weeks (as it's hard to read and long) but it will teach you a lot about C++ and what can be done with it.
相关文章