在前面 Bazel 依赖缺失导致的 C++ 进程 coredump 问题分析 这篇文章,因为二进制使用了不同版本的 proto 对象,对象的内存布局不一致导致读、写成员的内存地址错乱,进而导致进程 crash 掉。但是当时并没有展开细聊下面的问题:
- 对象在内存中是怎么布局的?
- 成员方法是如何拿到成员变量的地址?
这些其实涉及 C++ 的对象模型,《深度探索 C++对象模型:Inside the C++ Object Model 》这本书全面聊了这个问题,非常值得一读。不过这本书读起来并不容易,有的内容读过后如果没有加以实践,也很难完全理解。
于是好好梳理了下,主要结论:
- 对象的内存布局是连续的,成员变量按照声明的顺序存储在对象中,编译器会根据类定义计算每个成员变量相对于对象起始地址的偏移量。
- 成员方法存储在进程的文本段,不占用对象实例的内存空间,通过 this 指针和偏移量访问成员变量。
- 私有成员变量和方法在运行期并没有保护,可以通过地址偏移绕过编译器的限制进行读写,但是不推荐这样做。
- 静态成员变量和静态成员方法存储在程序的数据段和代码段,不占用对象实例的内存空间。
继承类的内存布局,编译器一般会把基类的成员变量放在派生类的成员变量之前,使对象模型变得更简单和直观。
- 带有虚函数的继承,对象的内存布局中包含虚函数表指针,多态调用通过虚函数表实现。虚函数实现比较复杂,这里只考虑简单的单继承。
- 地址空间布局随机化( ASLR )是现代操作系统的安全特性,可以有效防止缓冲区溢出攻击等安全漏洞。GDB 默认禁用 ASLR ,可以通过 set disable-randomization off 命令开启地址空间的随机化。
完整文章在:
结合实例深入理解 C++ 对象的内存布局
大家帮忙 review 下~