概述
目录
一.程序在内存中各个区域的分布(这里指的是物理内存中)
二.几个基本的汇编指令
三.常用寄存器
四.实例运行
参考:
https://blog.csdn.net/Jbinbin/article/details/87627806
https://www.cnblogs.com/aliflycoris/p/5746143.html
https://zhuanlan.zhihu.com/p/136174080
https://blog.csdn.net/ccboby/article/details/6042380
https://blog.csdn.net/mohan90118/article/details/47334199
https://blog.csdn.net/happylife1527/article/details/8072500
一.程序在内存中各个区域的分布(这里指的是物理内存中)
栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。
堆区(heap):堆内存只在程序运行时出现,一般由程序员手动分配和释放,一般可以使用malloc() & free() 函数来申请、释放。在操作系统下,如果程序员没释放,一般操作系统可以在程序结束后回收内存。
全局区(静态区):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放。
常量区:字符串常量和其他常量的存储位置,程序结束后由操作系统释放。
代码段(代码区):用来存放程序语句进行编译后,形成的机器代码。即存放函数体的二进制代码。
二.几个基本的汇编指令
- call:将当前指令的“下一条指令的地址”保存到栈中,然后跳转至目标函数的地址。
- ret :弹出栈顶元素,上一个函数的“下一条指令的地址”,并转至该地址。
- push:从栈顶入栈称为push,默认esp--。
- pop:从栈底出栈称为pop,默认esp++。
- mov:移动赋值,比如“mov ebp,esp”,将esp的值赋给ebp。
- sub:减法,比如“sub esp,0E4h”,将esp减去0E4h。
- add:加法,比如“add esp,8”,将esp加上8。
- lea:赋值,比如“lea edi,[ebp+FFFFFF1Ch]”,和mov的区别是右值可以先做加法再赋值
- stos:串行存储指令,它实现把eax中的数据放入到edi所指的地址中,同时edi后移4个字节,这里的stos实际上对应的是stosd,其他的还有stosb,stosw分别对应1,2个字节。sotre string 保存字符串之意。
- rep stos:rep是指循环执行ecx次操作,结合stos就是将eax的值填充ecx次每次填充完就edi+=4个字节,这样就可以重定位edi==ebp,并且对栈帧做值初始化,语法如下:
lea edi,[ebp+FFFFFF1Ch]
mov ecx,39h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
- dword:双字 就是四个字节。
- ptr:pointer缩写 即指针。
- []:里的数据是一个地址值,这个地址指向一个双字型数据,比如“mov eax, dword ptr [12345678]” 把内存地址12345678中的双字型(32位)数据赋给eax。
-
jmp:无条件跳转指令,对应于大量的条件跳转指令。
-
cmp:比较大小指令,结果用来设置标志位。
三.常用寄存器
在CPU中:读取指令(内存-->CPU)-->分析指令(CPU)-->执行指令(CPU)
- eax:累积暂存器,
- ebx:基底暂存器,
- ecx:计数暂存器,
- edx:资料暂存器
- edi、esi:变址寄存器
- esp:栈顶指针寄存器
- ebp:栈底指针寄存器
四.实例运行
int myadd(int x, int y) {
int m = 3;
int n = 4;
int z = x + y + m + n;
return z;
}
int main() {
int a = 1;
int b = 2;
int c = myadd(a, b);
return 0;
}
VS调试模式下,按Ctrl+F11,就可以打开反汇编窗口。
我是在win32下编译的,所以每行指令的地址(如下)是8位的16进制数!(因为每一位是代表4bit,8位代表32bit,32bit的地址可以访问2^32B的内存空间,也就是4G)
00B81850
00B81851
00B81853
00B81859
00B8185A
如果是x64下编译的,指令的地址是16位的16进制数!(因为每一位是代表4bit,16位代表64bit,64bit的地址可以访问2^64B的内存空间)
00007FF756BE1700
00007FF756BE1702
00007FF756BE1703
这里,指令的地址指的是虚拟内存地址,
而,调试过程中的ebp、esp、edi、esi等存放的地址是实际的物理地址!
也就是说在实际运行中,cpu会根据需要从虚拟地址空间中取出代码段放到内存中的代码区(这里涉及到《现代操作系统》中的“虚拟内存”,这里不展开介绍)
汇编指令程序:
main函数
--- e:汇编测试源.cpp --------------------------------------------------------------
int main() {
00B81850 push ebp
00B81851 mov ebp,esp
00B81853 sub esp,0E4h
00B81859 push ebx
00B8185A push esi
00B8185B push edi
00B8185C lea edi,[ebp+FFFFFF1Ch]
00B81862 mov ecx,39h
00B81867 mov eax,0CCCCCCCCh
00B8186C rep stos dword ptr es:[edi]
int a = 1;
00B8186E mov dword ptr [ebp-8],1
int b = 2;
00B81875 mov dword ptr [ebp-14h],2
int c = myadd(a, b);
00B8187C mov eax,dword ptr [ebp-14h]
00B8187F push eax
00B81880 mov ecx,dword ptr [ebp-8]
00B81883 push ecx
00B81884 call 00B813E8
00B81889 add esp,8
00B8188C mov dword ptr [ebp-20h],eax
return 0;
00B8188F xor eax,eax
}
00B81891 pop edi
00B81892 pop esi
00B81893 pop ebx
00B81894 add esp,0E4h
00B8189A cmp ebp,esp
00B8189C call 00B8114F
00B818A1 mov esp,ebp
00B818A3 pop ebp
00B818A4 ret
跳转:
_mainCRTStartup:
00B8105A jmp 00B82060
myadd:
00B813E8 jmp 00B817D0
__RTC_CheckEsp:
00B8114F jmp 00B81B60
00B81B60 bnd jne 00B81B65
esperror:
00B81B65 push ebp
myadd函数:
--- e:汇编测试源.cpp --------------------------------------------------------------
int myadd(int x, int y) {
00B817D0 push ebp
00B817D1 mov ebp,esp
00B817D3 sub esp,0E4h
00B817D9 push ebx
00B817DA push esi
00B817DB push edi
00B817DC lea edi,[ebp+FFFFFF1Ch]
00B817E2 mov ecx,39h
00B817E7 mov eax,0CCCCCCCCh
00B817EC rep stos dword ptr es:[edi]
int m = 3;
00B817EE mov dword ptr [ebp-8],3
int n = 4;
00B817F5 mov dword ptr [ebp-14h],4
int z = x + y + m + n;
00B817FC mov eax,dword ptr [ebp+8]
00B817FF add eax,dword ptr [ebp+0Ch]
00B81802 add eax,dword ptr [ebp-8]
00B81805 add eax,dword ptr [ebp-14h]
00B81808 mov dword ptr [ebp-20h],eax
return z;
00B8180B mov eax,dword ptr [ebp-20h]
}
00B8180E pop edi
00B8180F pop esi
00B81810 pop ebx
00B81811 mov esp,ebp
00B81813 pop ebp
00B81814 ret
接下来结合图表详细展示汇编的过程:
main函数执行之前其实有一个_mainCRTStartup函数,
操作系统装载应用程序后,做完初始化工作就转到程序的入口点执行。程序的默认入口点由连接程序设置, 不同的连接器选择的入口函数也不尽相同。在VC++下,连接器对控制台程序设置的入口函数是 _mainCRTStartup,_mainCRTStartup 再调用main 函数
符号的右上标,比如(0),是指该变量对应流程(0)得到的结果,ebp寄存器只有一个,右上标只是标记它的状态,具体值可以对照每张图中的寄存器状态表。
黑色方框代表内存中的栈,向下增长~
由_mainCRTStartup调用main函数,并在栈中创建了一个栈帧,
首先压入上一个函数的ebp0,划定范围大小为E4h(十六进制),对应十进制是228个内存单元
保存各个ebx、esi、edi的值(每个函数开始运行前都需要这样做)//这里的edi是上个函数的ebp
重定位edi的值,edi==当前函数的栈帧的ebp,也就是edi(10)==ebp(2)
下面继续按照程序走,图中列出的指令执行的同时会改变寄存器表中相对应的寄存器
到这里main函数的调用就结束了,然后执行接下来的三行代码,是常规的函数返回语句。
mov esp,ebp
pop ebp
ret
这里顺便再说一个小技巧:
在VS中的反汇编窗口的上方有一个“地址(A)”,可以用来查看指定地址处的指令。
比如有指令“call 000A130C”
那么,在对话框中输入 0x000A130C,回车,就会跳转到
myadd:
000A130C jmp 000A1690
再次输入0x000A1690,回车,就会跳转到myadd函数的汇编代码
最后
以上就是自信音响为你收集整理的【C语言编译】C语言的函数调用的过程的全部内容,希望文章能够帮你解决【C语言编译】C语言的函数调用的过程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复