【C语言编译】C语言的函数调用的过程

目录

一.程序在内存中各个区域的分布(这里指的是物理内存中)

二.几个基本的汇编指令

三.常用寄存器

四.实例运行


 

参考:

 

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)

  1. eax:累积暂存器,
  2. ebx:基底暂存器,
  3. ecx:计数暂存器,
  4. edx:资料暂存器
  5. edi、esi:变址寄存器
  6. esp:栈顶指针寄存器
  7. 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函数的汇编代码

  • 11
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值