这里以单继承为例,汇编采用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)