编译后的C++;班级看起来怎么样? 在汇编指令和C程序中有一些背景,我可以想象编译函数的样子,但很有趣,我从来没有仔细考虑过编译过的C++类的样子。 bash$ cat class.cpp #include<iostream> class Base { int i; float f; }; bash$ g++ -c class.cpp
但我得到的东西让我很难理解 有人能给我解释一下或建议一些好的起点吗。试试 g++-S class.cpp 这将为您提供一个程序集文件“class.s”(文本文件),您可以使用文本编辑器读取该文件。编译后的C++;班级看起来怎么样? 在汇编指令和C程序中有一些背景,我可以想象编译函数的样子,但很有趣,我从来没有仔细考虑过编译过的C++类的样子。 bash$ cat class.cpp #include<iostream> class Base { int i; float f; }; bash$ g++ -c class.cpp,c++,compiler-construction,linker,elf,C++,Compiler Construction,Linker,Elf,但我得到的东西让我很难理解 有人能给我解释一下或建议一些好的起点吗。试试 g++-S class.cpp 这将为您提供一个程序集文件“class.s”(文本文件),您可以使用文本编辑器读取该文件。 但是,您的代码不做任何事情(声明类本身不会生成代码),因此程序集文件中不会有太多内容。确定。编译类没有什么特别之处。编译类甚至不存在。存在的对象是平坦的内存块,字段之间可能有填充?和代码中的某个独立成员函数,它们将指向对象的指针作为第一个参数 所以类Base的对象应该是 (*基本地址):i (*基址+
但是,您的代码不做任何事情(声明类本身不会生成代码),因此程序集文件中不会有太多内容。确定。编译类没有什么特别之处。编译类甚至不存在。存在的对象是平坦的内存块,字段之间可能有填充?和代码中的某个独立成员函数,它们将指向对象的指针作为第一个参数 所以类Base的对象应该是 (*基本地址):i (*基址+sizeof(int)):f 有可能在田地之间有填充物吗?但这是特定于硬件的。基于处理器的内存模型 还有。。。在调试版本中,可以捕获调试符号中的类描述。但这是特定于编译器的。您应该搜索一个为编译器转储调试符号的程序。“编译类”是指“编译方法” 方法是一个带有额外参数的普通函数,通常放在寄存器中(我相信大部分是%ecx,这至少适用于大多数必须使用_thiscall约定生成COM对象的Windows编译器) <> P>类C++类与普通函数没有太大的区别,除了名称VMLIVE和构造函数/析构函数中设置VTABLE的一些神奇性。 < P>类(或多或少)被构造为规则结构。这些方法(或多或少…)被转换成函数,其中第一个参数是“this”。对类变量的引用作为对“this”的偏移
关于继承,让我们从C++ FAQ Lite引用,这里是镜像的。本章介绍如何在实际硬件中调用虚拟函数(编译在机器代码中是如何生成的)
让我们举个例子。假设类Base有5个虚拟函数:
virt0()
到virt4()
步骤#1:编译器构建一个包含5个函数指针的静态表,将该表埋在某个静态内存中。很多(不是全部)编译器在编译定义Base的第一个非内联虚拟函数的.cpp时定义此表。我们将该表称为v表;让我们假设其技术名称为Base::u vtable
。如果函数指针适合目标硬件平台上的一个机器字,Base::u vtable
将消耗5个隐藏wo内存的rds。不是每个实例5个,不是每个函数5个;只有5个。它可能看起来像以下伪代码:
// 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:编译器向类基的每个对象添加一个隐藏指针(通常也是一个机器字)。这称为v指针。将此隐藏指针视为隐藏数据成员,就像编译器将类重写为以下内容:
// Your original C++ source code
class Base {
public:
...
FunctionPtr* __vptr; ← supplied by the compiler, hidden from the programmer
...
};
// Your original C++ code
void mycode(Base* p)
{
p->virt3();
}
// Pseudo-code that the compiler generates from your C++
void mycode(Base* p)
{
p->__vptr[3](p);
}
步骤#3:编译器在每个构造函数中初始化this->\uu vptr
。其思想是使每个对象的v指针指向其类的v表,就像它在每个构造函数的初始化列表中添加以下指令一样:
Base::Base(...arbitrary params...)
: __vptr(&Base::__vtable[0]) ← supplied by the compiler, hidden from the programmer
...
{
...
}
现在让我们来推导一个派生类。假设C++代码定义继承自类基类的类DER。编译器重复步骤1和3(而不是2)。。在步骤1中,编译器创建一个隐藏的v表,保留与
Base::\uu vtable
中相同的函数指针,但替换与重写相对应的插槽。例如,如果Der通过virt2()
重写virt0()
,并继承其他的函数指针,则Der的v表可能看起来像这样(假装Der没有添加任何新的虚拟对象):
在步骤#3中,编译器在每个Der构造函数的开头添加一个类似的指针赋值。其思想是更改每个Der对象的v指针,使其指向其类的v表。(这不是第二个v指针;它是在基类base中定义的同一个v指针;请记住,编译器不会在类Der中重复步骤2。)
最后,让我们看看编译器如何实现对虚拟函数的调用。您的代码可能如下所示:
// Your original C++ source code
class Base {
public:
...
FunctionPtr* __vptr; ← supplied by the compiler, hidden from the programmer
...
};
// Your original C++ code
void mycode(Base* p)
{
p->virt3();
}
// Pseudo-code that the compiler generates from your C++
void mycode(Base* p)
{
p->__vptr[3](p);
}
编译器不知道这是要调用Base::virt3()
还是Der::virt3()
或者是另一个甚至还不存在的派生类的virt3()
方法。它只知道您正在调用virt3()
这恰好是v-table插槽#3中的函数。它将该调用重写为如下内容:
// Your original C++ source code
class Base {
public:
...
FunctionPtr* __vptr; ← supplied by the compiler, hidden from the programmer
...
};
// Your original C++ code
void mycode(Base* p)
{
p->virt3();
}
// Pseudo-code that the compiler generates from your C++
void mycode(Base* p)
{
p->__vptr[3](p);
}
我强烈建议每个C++开发者阅读FAQ。可能需要几个星期(因为它很难读和长)。但是C++会对C++对象文件有很大的不同。 与C对象文件的主要区别是C++方法名称。你可以试着使用选项<代码> -C - DEMange<代码> > <代码> > ObjdPU< /COL> > < P>,像一个C结构和一组附加的参数,这是一个指向结构的指针。 遵循编译器所做的最简单的方法可能是在不进行优化的情况下构建代码,然后将代码加载到调试器中,并以混合源代码/汇编程序模式逐步执行
但是,编译器的要点是,您不需要知道这些东西(除非您正在编写编译器)。它看起来与C结构非常相似。对!因此,这让我想到了如何编译结构,并且我意识到,从.bash$cat开始,我可能不理解这一部分