Inside The C++ Object Model简单记录
此篇为阅读《Inside The C++ Object Model》时对其中相对重要的 data 语义和 function 语义的一些记录
data语义
编译器一般将多个access sections连锁在一起形成一个区块,这个操作不会降低效率,诸如多个public域
C\C++的边界调整有可能会在中间插入若干bit(类似c的结构体内存对齐)
编译器会自动生成一些内容以支撑对象例如vptr,一般vptr会被插入对象的开头或结尾,依赖编译器的处理
1 | // static data member 不从属于class |
所以这时,当两个class都有同名static data member则会产生冲突,此时编译器为每个static data member编码(name-mangling),用以区分彼此
nonstatic data member在存取时和C的效率没什么两样,是C++从C中借鉴过来的一部分
存取需要通过明指或暗指(this)
但当用指针且继承结构中存在虚拟继承时就无法在编译阶段确定成员属于何对象(虚拟继承不常用,可忽略)
1 | object.x; |
派生类赋值给基类(但vs似乎避免了这个情况?),会将非继承成员放到基类因内存对齐而填充的空间中,此时基类结构发生改变,这时当另一个基类给当前基类赋值时,基类结构中的子类成员会被未知数据填充
vptr在尾部就兼容C,在头部更有利于多继承,vs中vptr被插入至头部,例子见下
单继承体系中把一个派生类地址给基类指针是一个自然过程,但在vptr在头部且派生类中含有虚函数时就需要编译器介入,多继承+虚拟继承就更需要了
多重继承,在指针变化时内部会进行计算以获得目标基类的offset(编译器介入)
编译器优化之后,封装不会对执行期效率产生什么影响
function 语意
类成员函数有3种状态:static, nonstatic, vitual
类内函数成员在编译器的优化后可以获得不低于外部函数的效率,会被编译器优化成了类外部函数实体
vitual member function
1 | // 用指针对虚函数的调用 |
C++多态:以一个基类指针或引用寻址出一个派生类对象
如何在执行期确定虚函数的实体?
- 具体是哪个类(指针或引用指向的真实类型)
- 哪个虚函数(虚函数地址)
如何存放两个需要的信息?
- 一个由编译器提供的vptr指针,指向vitual table,其中存储了type_info for object和 虚函数的执行期地址
- 为了找到函数地址,每个虚函数被指定一个索引(纯虚函数同样有自己的索引
在编译时虚函数由其对象调用可知,由编译器完全控制,唯一一个在执行期才知道的消息是:slot指向的函数实体
指针的类型是定义时确定的,但指向的对象的类型不确定,诸如:base *p = &child;
其中p是基类指针,但可以指向派生类对象,对象信息存放在virtual table中的RTT字段,运行时确定
非成员函数,成员函数、静态函数被编译器优化成完全相同的形式,函数效率相同
加上vitual后,随继承结构复杂度耗时增加
普通成员函数
同数据成员一样,直接是在内存中的真正地址,需要依赖于对象访问
inline
是**#define的一种安全替代品**,但仍然需要被小心处理,过多的参数(产生临时变量)和嵌套会导致扩展码大量增加或无法扩展开来
inline函数有惊人的效率,被视作不变表达式,编译器将其提出至循环之外,只计算一次
类结构实例研究
1 | struct Test { |
基类与派生类结构如下
1 | //-----vs编译后的内存布局-----// |