C++ 内存布局 - Part5: 继承关系中 构造析构与vptr的调整

这里以单继承为例,汇编采用AT&T格式,先看示例代码:

cpp 复制代码
#include <iostream>

class Base {
public:
    Base() {
            std::cout << "Base Constructor, this ptr: " << this << std::endl;
            printVptr();
    }
    virtual ~Base() {
            std::cout << "Base Destructor, this ptr: " << this << std::endl;
            printVptr();
    }
    virtual void printVptr() { std::cout << "Base::printVptr, vptr = " << *(void **)this << std::endl; }

    int a;
    int b;
};

class Derived : public Base {
public:
    Derived() {
            std::cout << "Derived Constructor, this ptr: " << this << std::endl;
            printVptr();
    }
    ~Derived() {
        std::cout << "Derived Destructor, this ptr: " << this << std::endl;
        printVptr();

    }
    void printVptr() override { std::cout << "Derived::printVptr, vptr = " << *(void **)this << std::endl; }

    int c;
};

int main() {
    std::cout << "Base size: " << sizeof(Base) << std::endl;
    std::cout << "Derived size: " << sizeof(Derived) << std::endl;

    Base *obj = new Derived();
    std::cout << "before delete, this ptr is: " << obj << std::endl;
    delete obj;
    return 0;
}

编译运行结果:

bash 复制代码
Base size: 16
Derived size: 24
Base Constructor, this ptr: 0xa1a2c0
Base::printVptr, vptr = 0x401090
Derived Constructor, this ptr: 0xa1a2c0
Derived::printVptr, vptr = 0x401068
before delete, this ptr is: 0xa1a2c0
Derived Destructor, this ptr: 0xa1a2c0
Derived::printVptr, vptr = 0x401068
Base Destructor, this ptr: 0xa1a2c0
Base::printVptr, vptr = 0x401090

从结果中可以看到, 在执行Base部分的构造和析构时,vptr指向的是基类的虚表,执行Derived部分的构造和析构时,vptr指向的是派生类的虚表。

查看汇编代码,以Base的析构为例,其他的构造析构都是类似:

bash 复制代码
Base::~Base() [base object destructor]:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $16, %rsp
        movq    %rdi, -8(%rbp)             将this指针入栈
        movl    $vtable for Base+16, %edx  将虚表指针存入edx
        movq    -8(%rbp), %rax             将this指针存入rax
        movq    %rdx, (%rax)  将虚表指针存入rax指向的内存,也就是对象内存的开始部分,vptr
        movl    $.LC1, %esi
        movl    $_ZSt4cout, %edi
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        movq    %rax, %rdx
        movq    -8(%rbp), %rax
        movq    %rax, %rsi
        movq    %rdx, %rdi
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(void const*)
        movl    $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, %esi
        movq    %rax, %rdi
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
        movq    -8(%rbp), %rax
        movq    %rax, %rdi
        call    Base::printVptr()
        nop
        leave
        ret

因此,整个构造析构的顺序为:

当new一个派生类对象时,首先会执行基类的构造函数,这时这个构造中的对象,其vptr指向基类的虚表,当基类部分构造完毕,继续执行派生类的构造函数时,此时对象的vptr指向派生类的虚表。

当delete这个派生类对象时,首先执行派生类的析构函数,此时对象的vptr仍然指向派生类的虚表,派生类的析构函数会继续执行基类的析构函数,此时对象的vptr会指向基类的虚表。最后派生类的析构函数会负责释放整个对象内存: call operator delete(void*, unsigned long)

相关推荐
Biomamba生信基地9 分钟前
两天入门R语言,周末开讲
开发语言·r语言·生信
RAN_PAND10 分钟前
STL介绍1:vector、pair、string、queue、map
开发语言·c++·算法
Bio Coder13 分钟前
R语言安装生物信息数据库包
开发语言·数据库·r语言
Tiger Z13 分钟前
R 语言科研绘图第 27 期 --- 密度图-分组
开发语言·程序人生·r语言·贴图
life_time_3 小时前
C语言(22)
c语言·开发语言
Minner-Scrapy3 小时前
DApp 开发入门指南
开发语言·python·web app
mit6.8243 小时前
[实现Rpc] 通信-Muduo库的实现 | && 完美转发 | reserve | unique_lock
c++·网络协议·rpc
孤雪心殇3 小时前
简单易懂,解析Go语言中的Map
开发语言·数据结构·后端·golang·go
庸俗今天不摸鱼3 小时前
Canvas进阶-4、边界检测(流光,鼠标拖尾)
开发语言·前端·javascript·计算机外设