关于c语言在下的调用过程描述(IA32)
- 关于函数调用指令call指令
该指令将PC压栈,然后跳转,也就是图中的返回地址,此时sp指向返回地址
- 入栈 sp先减,后写入数据;
- 准备阶段
- pushl %ebp,此时esp指向函数栈帧底部(存放上一个函数的栈帧底部位置)
- movl %esp,%ebp,此时esp 和ebp指向了函数栈帧底部
- subl 分配栈空间
- 设置局部变量
- 准备入口参数(X64架构下,参数优先放在寄存器中)
- 处理返回参数
- 结束
- movl %ebp,%esp 将栈顶置为被调用函数栈底
- pop %ebp 将bp置为原调用函数栈底,并且将sp还原为返回地址处
关于c++ 类在反汇编中的体现
classTest.cpp1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class T{ int a; public: int b; void test(){ a=3; } }; int main(){ T a; a.test(); return 0; }
|
通过objdump -d 查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 00000000004005ad <main>: 4005ad: 55 push %rbp 4005ae: 48 89 e5 mov %rsp,%rbp 4005b1: 48 83 ec 10 sub $0x10,%rsp 4005b5: 48 8d 45 f0 lea -0x10(%rbp),%rax 4005b9: 48 89 c7 mov %rax,%rdi 4005bc: e8 07 00 00 00 callq 4005c8 <_ZN1T4testEv> 4005c1: b8 00 00 00 00 mov $0x0,%eax 4005c6: c9 leaveq 4005c7: c3 retq
00000000004005c8 <_ZN1T4testEv>: 4005c8: 55 push %rbp 4005c9: 48 89 e5 mov %rsp,%rbp 4005cc: 48 89 7d f8 mov %rdi,-0x8(%rbp) 4005d0: 48 8b 45 f8 mov -0x8(%rbp),%rax 4005d4: c7 00 03 00 00 00 movl $0x3,(%rax) 4005da: 5d pop %rbp 4005db: c3 retq 4005dc: 0f 1f 40 00 nopl 0x0(%rax)
|
-
目前可以推断出来的信息
-
4005b5: 48 8d 45 f0 lea -0x10(%rbp),%rax //
4005b9: 48 89 c7 mov %rax,%rdi
4005bc: e8 07 00 00 00 callq 4005c8 <_ZN1T4testEv> //对于类的函数来说,g++编译将其看作一个全局符号,其具有绝对的地址
-
这个例子中很明确的可以看出
-
1.main函数中对于栈类进行空间分配的方式,由于没有显示的调用某个特定的构造函数,所以此处直接分配了16个空间其中位于底地址的4字节存放了该实例化 a.a的数据
-
2.调用实例化a.test的过程
- %rdi 存放了实例化a的地址,实际上该地址也就是a.a的地址
- 4005d4: c7 00 03 00 00 00 movl $0x3,(%rax) 此时rax就是rdi 也就是a.a的栈地址,执行a=3;
- 如果执行b=3 那么就是movl $0x3,0x04(%rax)
-
结论:
- 对于类/结构体来说,其内存的布局方式都是相同的,变量占用空间,类中的函数当作全局符号进行解析
- 类并没有什么难以理解的,全局变量对于c++来说其作用域就是文件单元;类的作用域就是类,两者只是一种组织方式
- 对于类而言,其内部的变量基本上都被处理为local var,分配在栈或者堆上,并不是全局变量存放在data中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class T{ int a; public: int b; void test(){ a=3; } }; int main(){ for(int index=0;index++;index<10){ T a; a.test(); } return 0; }
|
汇编代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 00000000004005ad <main>: 4005ad: 55 push %rbp 4005ae: 48 89 e5 mov %rsp,%rbp 4005b1: 48 83 ec 10 sub $0x10,%rsp 4005b5: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 4005bc: eb 0c jmp 4005ca <main+0x1d> 4005be: 48 8d 45 f0 lea -0x10(%rbp),%rax //循环内部代码 4005c2: 48 89 c7 mov %rax,%rdi 4005c5: e8 1a 00 00 00 callq 4005e4 <_ZN1T4testEv> 4005ca: 8b 45 fc mov -0x4(%rbp),%eax //循环判断代码 4005cd: 8d 50 01 lea 0x1(%rax),%edx 4005d0: 89 55 fc mov %edx,-0x4(%rbp) 4005d3: 85 c0 test %eax,%eax 4005d5: 0f 95 c0 setne %al 4005d8: 84 c0 test %al,%al 4005da: 75 e2 jne 4005be <main+0x11> 4005dc: b8 00 00 00 00 mov $0x0,%eax 4005e1: c9 leaveq 4005e2: c3 retq 4005e3: 90 nop
|
对于循环内部生成空间会被本身循环利用,如反复产生的a对象,而类的析构函数不是用来处理栈分配的空间,只有在堆中创建析构才是重要的
对于循环体结束后,栈分配的空间即使没有回收也不能进行调用了,此处可以使用指针访问到,数据是否正确这和编译器优化有关,比如说此处编译器将内存回收,填上其他值;并且在c++的语法中也不推荐去调用此处,但是如果通过指针应该还是可以访问到的.