带继承与指针的C++类内存布局疑问
带继承与指针的C++类内存布局疑问
嗨,我来帮你拆解这个内存布局的问题~首先先把你的代码和输出整理清楚:
你的代码
class Msg { public: int info1; int info2; char* msg; }; class SpeMsg : public Msg { public: struct speInfos { int speInfo1; int speInfo2; }; speInfos spe; }; int main() { SpeMsg* sm = new SpeMsg(); sm->info1 = 5; sm->info2 = 10; sm->spe.speInfo1 = 15; sm->spe.speInfo2 = 20; sm->msg = new char[sizeof("25")]; memcpy(sm->msg, "25", sizeof("25")); uint8_t * payload = (uint8_t*)sm; SpeMsg::speInfos* sI = &sm->spe; cout << "Info 1 : " << &sm->info1 << endl; cout << "Info 2 : " <<&sm->info2 << endl; cout << "msg : " <<&sm->msg << endl; cout << "pointing to : " <<(void *)sm->msg << endl; cout << "SpeInfo1 : " <<&sm->spe.speInfo1 << endl; cout << "SpeInfo2 : " <<&sm->spe.speInfo2 << endl; delete sm->msg; delete sm; return 0; }
输出结果
Info 1 : 0x1515eb0
Info 2 : 0x1515eb4
msg : 0x1515eb8
pointing to : 0x1515ed0
SpeInfo1 : 0x1515ec0
SpeInfo2 : 0x1515ec4
首先要先澄清一个你可能混淆的点:你提到的“msg address is 0x1515ed0”其实是**msg指针指向的内存地址**,而msg这个成员变量本身在SpeMsg对象里的地址是0x1515eb8——这是两个完全不同的东西,前者是动态分配的字符串内存,后者是对象里存储指针的那个位置。
接下来解释为什么spe.speInfo1的地址是0x1515ec0而不是你预期的0x1515ec8:这完全是C++内存对齐规则导致的,不是随机的。
我们一步步拆解对象的内存布局(假设你是在64位系统下编译,这是现在的主流环境):
基类
Msg的成员布局:info1是int,占4字节,地址从0x1515eb0开始,到0x1515eb3结束info2是int,占4字节,紧跟在info1后面,地址0x1515eb4到0x1515eb7msg是char*指针,在64位系统下占8字节,所以它的起始地址是0x1515eb8,结束地址是0x1515ebf(8字节)- 基类
Msg的总大小是4+4+8=16字节,刚好是8字节的倍数(64位系统下,指针大小是8字节,内存对齐通常要求类的大小是最大成员大小的倍数),所以不需要额外的填充字节。
派生类
SpeMsg继承自Msg,所以它的内存布局是先包含基类Msg的所有成员,再加上自己的成员spe:- 基类
Msg的内存结束于0x1515eb0 + 16 = 0x1515ec0,所以派生类的spe成员就从这个地址开始 spe.speInfo1是int,占4字节,地址0x1515ec0到0x1515ec3spe.speInfo2是int,占4字节,紧跟其后,地址0x1515ec4到0x1515ec7
- 基类
你之前误以为speInfo2之后才是msg的位置,但其实msg是基类里的成员,早就放在了0x1515eb8的位置,而spe是派生类添加的成员,紧跟在基类的内存之后。而0x1515ed0是你通过new char[]动态分配的字符串内存,和SpeMsg对象本身的内存布局完全无关,它的地址是操作系统在堆的另一块区域随机分配的。
总结一下:
- 对象内部的成员是按照继承顺序和声明顺序布局的,同时要遵循内存对齐规则
- 指针成员的存储地址(对象里的位置)和它指向的地址(动态分配的内存)是完全独立的两个概念
备注:内容来源于stack exchange,提问作者Kaldir




