C++继承体系中this指针地址不一致的原因探究
C++多继承中this指针地址不一致的原因分析
在调试遗留代码时发现,C++多继承体系中不同基类视角下的this指针地址存在差异,以下是复现示例:
复现代码
#include <cstdio> #include <vector> class ROOT1 { public: ROOT1() {} void printsomething() { printf("ROOT1::printsomething %p\n", this); } virtual ~ROOT1() {} }; class SUBROOT : public ROOT1 { public: SUBROOT() {} void printsomething() { printf("SUBROOT::printsomething %p\n", this); } virtual ~SUBROOT() {} }; class ROOT2 { public: ROOT2() {} virtual ~ROOT2() {} void printsomething() { printf("ROOT2::printsomething %p\n", this); } }; class CHILD : public ROOT2, public SUBROOT { public: CHILD() {} virtual ~CHILD() {} void printsomething() { printf("CHILD::printsomething %p\n", this); } }; class GRANDCHILD : public CHILD { public: GRANDCHILD() {} virtual ~GRANDCHILD() {} void printsomething() { printf("GRANDCHILD::printsomething %p\n", this); } }; int main() { try { GRANDCHILD* line = new GRANDCHILD; ROOT1* root1 = static_cast<ROOT1*>(line); SUBROOT* subroot = static_cast<SUBROOT*>(line); CHILD* child = static_cast<CHILD*>(line); ROOT2* root2 = static_cast<ROOT2*>(line); root2->printsomething(); root1->printsomething(); subroot->printsomething(); child->printsomething(); line->printsomething(); } catch (const char* s) { printf("Throw %s, %d\n", s, sizeof(std::vector<void*>)); } }
第一次运行输出
ROOT2::printsomething 0x23cf2b0 ROOT1::printsomething 0x23cf2b8 SUBROOT::printsomething 0x23cf2b8 CHILD::printsomething 0x23cf2b0 GRANDCHILD::printsomething 0x23cf2b0
调整继承顺序后的代码修改
将CHILD类的继承顺序调换:
class CHILD : public SUBROOT, public ROOT2
第二次运行输出
ROOT2::printsomething 0xc9b2b8 ROOT1::printsomething 0xc9b2b0 SUBROOT::printsomething 0xc9b2b0 CHILD::printsomething 0xc9b2b0 GRANDCHILD::printsomething 0xc9b2b0
原因分析
这是C++多继承机制下的正常行为,核心源于多继承对象的内存布局规则:
- 带有虚函数的类会包含一个虚函数表指针(vptr),用于实现多态。当类继承多个带虚函数的基类时,编译器会为每个这类基类分配独立的虚表,这些虚表指针会嵌入到派生类对象的内存中。
- 派生类对象的内存会严格按照继承声明的顺序,依次排列各个基类的子对象(包括它们的虚表指针)。
- 当把派生类指针转换为不同基类指针时,编译器会自动计算并调整指针的偏移量,使其指向对应基类子对象在派生类内存中的起始位置——这就是不同基类视角下
this地址不同的本质。
针对示例的具体解释:
当
CHILD按ROOT2 -> SUBROOT顺序继承时:ROOT2作为第一个基类,其子对象占据CHILD内存的起始位置,因此ROOT2*、CHILD*、GRANDCHILD*的this都指向这个起始地址。SUBROOT(及其父类ROOT1)的子对象被放在ROOT2子对象之后,偏移量等于一个虚表指针的大小(通常为8字节),所以SUBROOT*和ROOT1*的this指向偏移后的地址。
当调换为
SUBROOT -> ROOT2顺序继承时:SUBROOT(含ROOT1)的子对象占据内存起始位置,因此SUBROOT*、ROOT1*、CHILD*、GRANDCHILD*的this都指向这里。ROOT2的子对象被放在后面,偏移8字节,所以ROOT2*的this指向该偏移地址。
需要注意:这种指针偏移是编译器自动处理的,只要使用static_cast、dynamic_cast这类合法的类型转换,就不会引发内存访问问题;如果强行用reinterpret_cast跳过偏移调整,会直接导致非法内存访问。
内容的提问来源于stack exchange,提问作者Ameya Babhulgaonkar
相关产品推荐
相关产品推荐

