You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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地址不同的本质。

针对示例的具体解释:

  1. 当CHILD按ROOT2 -> SUBROOT顺序继承时:

    • ROOT2作为第一个基类,其子对象占据CHILD内存的起始位置,因此ROOT2*、CHILD*、GRANDCHILD*的this都指向这个起始地址。
    • SUBROOT(及其父类ROOT1)的子对象被放在ROOT2子对象之后,偏移量等于一个虚表指针的大小(通常为8字节),所以SUBROOT*和ROOT1*的this指向偏移后的地址。
  2. 当调换为SUBROOT -> ROOT2顺序继承时:

    • SUBROOT(含ROOT1)的子对象占据内存起始位置,因此SUBROOT*、ROOT1*、CHILD*、GRANDCHILD*的this都指向这里。
    • ROOT2的子对象被放在后面,偏移8字节,所以ROOT2*的this指向该偏移地址。

需要注意:这种指针偏移是编译器自动处理的,只要使用static_cast、dynamic_cast这类合法的类型转换,就不会引发内存访问问题;如果强行用reinterpret_cast跳过偏移调整,会直接导致非法内存访问。


内容的提问来源于stack exchange,提问作者Ameya Babhulgaonkar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 19:28:13