从汇编的角度了解C++原理——类的储存结构和函数调用
本文用到的反汇编工具是objconv,使用方法可以看我另一篇文章https://blog.csdn.net/weixin_45001971/article/details/128660642。
1、类的储存结构和函数调用
以这段代码为例。
编译后对obj文件反汇编,得到以下汇编代码,配合常量的值来分析汇编的含义。
main:
sub rsp, 72
mov dword [rsp+20H], 10 //栈顶偏移20个字节的位置定义为num变量
lea rcx, [rsp+28H] //把rsp+28H的指针放入rcx寄存器
call ??0A@@QEAA@XZ //A类型的构造函数
mov eax, 4294967295 //4294967295就是-1,eax寄存器用来存放返回值
add rsp, 72
ret
??0A@@QEAA@XZ:; Function begin //构造函数
mov qword [rsp+8H], rcx
mov rax, qword [rsp+8H] //把rcx寄存器的值赋到rax寄存器
mov dword [rax], 10 //定义rsp+28H的位置为d1变量
mov rax, qword [rsp+8H]
mov byte [rax+4H], 20 //定义rsp+28H+4H的位置为d2变量
mov rax, qword [rsp+8H]
mov dword [rax+8H], 30 //定义rsp+28H+8H的位置为d3变量
mov rax, qword [rsp+8H]
mov byte [rax+0CH], 40 //定义rsp+28H+0CH的位置为d4变量
mov rax, qword [rsp+8H]
ret
; ??0A@@QEAA@XZ End of function
从上面的例子可以看出以下几点。
第一点
类的函数执行过程分为以下两步:
1、把对象的地址放入rcx寄存器。
2、执行函数。
执行函数时,如果需要访问this指针,会先取rcx的值,作为this,然后再进一步操作。
第二点
类的储存结构遵循了4个字节的对齐原则,这跟我们在书本上的学的一样,d2变量虽然是char类型,只占一个字节,但d3和d2的首地址仍然相差4个字节,上述例子中A类的储存结构可以这样表示。
图画的不太好,简单示意一下。
除了前面说的之外,值得注意的是,struct和class以及private和public等写法在汇编代码里并没有体现出区别,所以这些关键字仅在编译阶段对语法的限制起作用。
验证
对例程做以下修改。
输出
与汇编里显示的一致。
2、子类的储存结构
修改代码如下:
反汇编得到。
main:
sub rsp, 72
lea rcx, [rsp+20H] //取this
call ??0B@@QEAA@XZ //构造
lea rcx, [rsp+20H]
call ?func1@A@@QEAAXXZ //调用A类的func1
lea rcx, [rsp+20H]
call ?func2@A@@QEAAXXZ //调用A类的func2
mov eax, 4294967295
add rsp, 72
ret
??0A@@QEAA@XZ:; Function begin //A类的构造函数
mov qword [rsp+8H], rcx
mov rax, qword [rsp+8H]
mov dword [rax], 10
mov rax, qword [rsp+8H]
mov byte [rax+4H], 20
mov rax, qword [rsp+8H]
mov dword [rax+8H], 30
mov rax, qword [rsp+8H]
mov byte [rax+0CH], 40
mov rax, qword [rsp+8H]
ret
??0B@@QEAA@XZ: //B类的构造函数
mov qword [rsp+8H], rcx //把this指针放在rsp+8H
sub rsp, 40 //函数压栈 rsp-40
mov rcx, qword [rsp+30H] //rsp+30H等价rsp+48,也就是把this放入rcx
call ??0A@@QEAA@XZ //调用A类的构造函数
mov rax, qword [rsp+30H]
mov dword [rax+10H], 50 //this指针往后偏移16字节的位置定义为d5
mov rax, qword [rsp+30H]
add rsp, 40 //出栈
ret
?func1@A@@QEAAXXZ:; Function begin
mov qword [rsp+8H], rcx
mov rax, qword [rsp+8H]
mov dword [rax], 11
ret
?func2@A@@QEAAXXZ:; Function begin
mov qword [rsp+8H], rcx
mov rax, qword [rsp+8H]
mov dword [rax], 12
ret
从示例中可以看到,子类中定义的成员,是拼接在父类成员的后面的,A类的大小是16,B类的成员d5被定义在this指针往后偏移16个字节的位置,如下图所示。
类里面的构造函数以及普通方法调用过程都是一样的,先把this指针存入rcx寄存器,然后执行函数的过程中从rcx寄存器取this指针。