测试源码与对应的反汇编代码
考虑如下一段c++源码 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Base {
public:
virtual void show() {
std::cout << "Base class show function" << std::endl;
}
};
class Derived : public Base {
public:
void show() override {
std::cout << "Derived class show function" << std::endl;
}
};
int main() {
Base* basePtr = new Base();
Base* derivedPtr = new Derived();
basePtr->show();
derivedPtr->show();
delete basePtr;
delete derivedPtr;
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50Dump of assembler code for function main():
0x0000590334da51e9 <+0>: endbr64
0x0000590334da51ed <+4>: push rbp
0x0000590334da51ee <+5>: mov rbp,rsp
0x0000590334da51f1 <+8>: push rbx
0x0000590334da51f2 <+9>: sub rsp,0x18
0x0000590334da51f6 <+13>: mov edi,0x8
0x0000590334da51fb <+18>: call 0x590334da50c0 <_Znwm@plt>
0x0000590334da5200 <+23>: mov rbx,rax
0x0000590334da5203 <+26>: mov QWORD PTR [rbx],0x0
0x0000590334da520a <+33>: mov rdi,rbx
0x0000590334da520d <+36>: call 0x590334da5372 <_ZN4BaseC2Ev>
0x0000590334da5212 <+41>: mov QWORD PTR [rbp-0x20],rbx
0x0000590334da5216 <+45>: mov edi,0x8
0x0000590334da521b <+50>: call 0x590334da50c0 <_Znwm@plt>
0x0000590334da5220 <+55>: mov rbx,rax
0x0000590334da5223 <+58>: mov QWORD PTR [rbx],0x0
0x0000590334da522a <+65>: mov rdi,rbx
0x0000590334da522d <+68>: call 0x590334da5390 <_ZN7DerivedC2Ev>
0x0000590334da5232 <+73>: mov QWORD PTR [rbp-0x18],rbx
0x0000590334da5236 <+77>: mov rax,QWORD PTR [rbp-0x20]
0x0000590334da523a <+81>: mov rax,QWORD PTR [rax]
0x0000590334da523d <+84>: mov rdx,QWORD PTR [rax]
0x0000590334da5240 <+87>: mov rax,QWORD PTR [rbp-0x20]
0x0000590334da5244 <+91>: mov rdi,rax
0x0000590334da5247 <+94>: call rdx
0x0000590334da5249 <+96>: mov rax,QWORD PTR [rbp-0x18]
0x0000590334da524d <+100>: mov rax,QWORD PTR [rax]
0x0000590334da5250 <+103>: mov rdx,QWORD PTR [rax]
0x0000590334da5253 <+106>: mov rax,QWORD PTR [rbp-0x18]
0x0000590334da5257 <+110>: mov rdi,rax
0x0000590334da525a <+113>: call rdx
0x0000590334da525c <+115>: mov rax,QWORD PTR [rbp-0x20]
0x0000590334da5260 <+119>: test rax,rax
0x0000590334da5263 <+122>: je 0x590334da5272 <main()+137>
0x0000590334da5265 <+124>: mov esi,0x8
0x0000590334da526a <+129>: mov rdi,rax
0x0000590334da526d <+132>: call 0x590334da50d0 <_ZdlPvm@plt>
0x0000590334da5272 <+137>: mov rax,QWORD PTR [rbp-0x18]
0x0000590334da5276 <+141>: test rax,rax
0x0000590334da5279 <+144>: je 0x590334da5288 <main()+159>
0x0000590334da527b <+146>: mov esi,0x8
0x0000590334da5280 <+151>: mov rdi,rax
0x0000590334da5283 <+154>: call 0x590334da50d0 <_ZdlPvm@plt>
0x0000590334da5288 <+159>: mov eax,0x0
0x0000590334da528d <+164>: add rsp,0x18
0x0000590334da5291 <+168>: pop rbx
0x0000590334da5292 <+169>: pop rbp
0x0000590334da5293 <+170>: ret
End of assembler dump.
详细分析
- 函数栈帧初始化 更新与设置好当前函数栈空间
1
2
3
4
50x0000590334da51e9 <+0>: endbr64
0x0000590334da51ed <+4>: push rbp
0x0000590334da51ee <+5>: mov rbp,rsp
0x0000590334da51f1 <+8>: push rbx
0x0000590334da51f2 <+9>: sub rsp,0x18 - 构造basePtr
1 | 0x0000590334da51f6 <+13>: mov edi,0x8 ;申请8字节空间 |
Base类没有成员函数,但是由于包含了虚函数,所以类中需要储存一个虚函数表对应的指针,64为机上一个指针8个字节,所以需要向系统申请8字节大小的空间。 tips: c++flit,指令可以将编译器按特定规则重命名后的函数名翻译回原始字符串,比如 c++filt _ZN4BaseC2Ev,将返回Base::Base()
其中Base::Base()的构造函数是编译器生成的 其汇编代码为: 1
2
3
4
5
6
7
8
9
10
11
12Dump of assembler code for function _ZN4BaseC2Ev:
0x00005db79e2ed372 <+0>: endbr64
0x00005db79e2ed376 <+4>: push rbp
0x00005db79e2ed377 <+5>: mov rbp,rsp
0x00005db79e2ed37a <+8>: mov QWORD PTR [rbp-0x8],rdi
0x00005db79e2ed37e <+12>: lea rdx,[rip+0x29cb] # 0x5db79e2efd50 <_ZTV4Base+16> ;取base的虚表指针
0x00005db79e2ed385 <+19>: mov rax,QWORD PTR [rbp-0x8]
0x00005db79e2ed389 <+23>: mov QWORD PTR [rax],rdx ;将虚表指针存在头部
0x00005db79e2ed38c <+26>: nop
0x00005db79e2ed38d <+27>: pop rbp
0x00005db79e2ed38e <+28>: ret
End of assembler dump.
1 | Dump of assembler code for function _ZN7DerivedC2Ev: |
可以看到,子类调用了基类的构造函数,同时覆盖掉了自己被基类构造函数构造好的虚函数表指针
虚函数调用过程
1 | 0x0000590334da5236 <+77>: mov rax,QWORD PTR [rbp-0x20] ;从栈上取基类的this指针 |
可以看到这里的虚函数调用不是直接通过call函数地址直接调用的,而是通过相对于虚表指针的便宜地址实现的函数调用,编译器只要保证对于子类和基类相同的函数名所对应的函数相对各自虚表的偏移地址是一致的,就可以实现运行时多态了。