我记得我在学校的时候,以及当初看学习视频的时候,总是被告诉C++对象构造的顺序,先调用父类的构造函数,后按照顺序调用各成员的构造函数,最后再调用当前类型的构造函数。反过来析构的时候,先调用当前类型析构函数,然后从下到上调用成员的析构函数,最后调用父类。
当初乍一听,并且在每个类的构造器和析构器中加打印,感觉老师说的没有问题!
但是我当时有一个疑问,C++编译器是如何控制这一套流程的呢?后来我发现老师说的并非全对。
看下面的例子:
普通构造,析构分析, 零优化的汇编代码,栈上创建对象的情况
#include <cstdio>
class Data {
public:
Data() { num = 10; }
~Data() {}
private:
int num = 0;
};
class Data1 {
public:
Data1() { num = 10; }
~Data1() {}
private:
int num = 0;
};
class Base {
public:
Base(char ch) : c{ch} {}
~Base() {}
private:
int num = 20;
char c = 'c';
};
class Derived : Base {
public:
Derived() : Base('a') {
::printf("hello world\n");
}
~Derived() {
::printf("hello world again\n");
}
Data data_;
Data1 data2_;
};
int main() {
Derived d;
return 0;
}
类Derived
继承了Base
类(暂时不考虑写虚析构的问题
),并有两个数据成员,他们的顺序为Data
,然后Data1
,在类Derived
的构造器中,有一侧打印。
编译环境是gcc x86-64
经过编译器生成汇编代码:
Data::Data() [base object constructor]:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax], 0
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax], 10
nop
pop rbp
ret
Data::~Data() [base object destructor]:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
nop
pop rbp
ret
Data1::Data1() [base object constructor]:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax], 0
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax], 10
nop
pop rbp
ret
Data1::~Data1() [base object destructor]:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
nop
pop rbp
ret
Base::Base(char) [base object constructor]:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
mov eax, esi
mov BYTE PTR [rbp-12], al
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax], 20
mov rax, QWORD PTR [rbp-8]
movzx edx, BYTE PTR [rbp-12]
mov BYTE PTR [rax+4], dl
nop
pop rbp
ret
Base::~Base() [base object destructor]:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
nop
pop rbp
ret
.LC0:
.string "hello world"
Derived::Derived() [base object constructor]:
push rbp
mov rbp, rsp
push rbx
sub rsp, 24
mov QWORD PTR [rbp-24], rdi
mov rax, QWORD PTR [rbp-24]
mov esi, 97
mov rdi, rax
call Base::Base(char) [base object constructor]
mov rax, QWORD PTR [rbp-24]
add rax, 8
mov rdi, rax
call Data::Data() [complete object constructor]
mov rax, QWORD PTR [rbp-24]
add rax, 12
mov rdi, rax
call Data1::Data1() [complete object constructor]
mov edi, OFFSET FLAT:.LC0
call puts
jmp .L10
mov rbx, rax
mov rax, QWORD PTR [rbp-24]
add rax, 12
mov rdi, rax
call Data1::~Data1() [complete object destructor]
mov rax, QWORD PTR [rbp-24]
add rax, 8
mov rdi, rax
call Data::~Data() [complete object destructor]
mov rax, QWORD PTR [rbp-24]
mov rdi, rax
call Base::~Base() [base object destructor]
mov rax, rbx
mov rdi, rax
call _Unwind_Resume
.L10:
mov rbx, QWORD PTR [rbp-8]
leave
ret
.LC1:
.string "hello world again"
Derived::~Derived() [base object destructor]:
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov edi, OFFSET FLAT:.LC1
call puts
mov rax, QWORD PTR [rbp-8]
add rax, 12
mov rdi, rax
call Data1::~Data1() [complete object destructor]
mov rax, QWORD PTR [rbp-8]
add rax, 8
mov rdi, rax
call Data::~Data() [complete object destructor]
mov rax, QWORD PTR [rbp-8]
mov rdi, rax
call Base::~Base() [base object destructor]
nop
leave
ret
main:
push rbp
mov rbp, rsp
push rbx
sub rsp, 24
lea rax, [rbp-32]
mov rdi, rax
call Derived::Derived() [complete object constructor]
mov ebx, 0
lea rax, [rbp-32]
mov rdi, rax
call Derived::~Derived() [complete object destructor]
mov eax, ebx
mov rbx, QWORD PTR [rbp-8]
leave
ret
分析主函数和Derived类的构造函数:
main:
push rbp
mov rbp, rsp
push rbx
sub rsp, 24
lea rax, [rbp-32]
mov rdi, rax
call Derived::Derived() [complete object constructor]
mov ebx, 0
lea rax, [rbp-32]
mov rdi, rax
call Derived::~Derived() [complete object destructor]
mov eax, ebx
add rsp, 24
pop rbx
pop rbp
ret
.LC0:
.string "hello world"
Derived::Derived() [base object constructor]:
push rbp
mov rbp, rsp
push rbx
sub rsp, 24
mov QWORD PTR [rbp-24], rdi
mov rax, QWORD PTR [rbp-24]
mov esi, 97
mov rdi, rax
call Base::Base(char) [base object constructor]
mov rax, QWORD PTR [rbp-24]
add rax, 8
mov rdi, rax
call Data::Data() [complete object constructor]
mov rax, QWORD PTR [rbp-24]
add rax, 12
mov rdi, rax
call Data1::Data1() [complete object constructor]
mov edi, OFFSET FLAT:.LC0
call puts
jmp .L13
mov rbx, rax
mov rax, QWORD PTR [rbp-24]
add rax, 12
mov rdi, rax
call Data1::~Data1() [complete object destructor]
mov rax, QWORD PTR [rbp-24]
add rax, 8
mov rdi, rax
call Data::~Data() [complete object destructor]
mov rax, QWORD PTR [rbp-24]
mov rdi, rax
call Base::~Base() [base object destructor]
mov rax, rbx
mov rdi, rax
call _Unwind_Resume
可以看到主函数内,构造Derived
对象调用了Derived
的构造函数,然后Derived
的构造函数内部首先会调用Base
,其次按顺序调用Data
和Data1
的构造函数,最后调用::printf打印,从形式上:"Base->Data->Data1->Derived"
的顺序而已,析构函数亦然。
或许你也可以打印一个调用栈看看 嘿嘿。我老师教的,还有网上乱教的 都是误人子弟
#include <execinfo.h>
#include <unistd.h>
#include <string>
std::string stackTrace() {
const int max_frames = 15;
void *frame[max_frames]{};
int nptrs = ::backtrace(frame, max_frames);
char **strings = ::backtrace_symbols(frame, nptrs);
if (not strings) {
return {};
}
std::string stack;
for (int i = 1; i < nptrs; ++i) {
if (nullptr == strings[i]) break;
stack += strings[i];
stack += "\n";
}
return stack;
}