C语言函数调用完整过程

按照调用约定传参

调用约定是调用方(Caller)和被调方(Callee)之间按相关标准

对函数的某些行为做出是商议,其中包括下面内容:

传参顺序:是从左往右传还是从右往左

传参方式:是用寄存器传还是使用内存传

平栈方式:是调用方平栈还是被调方平栈

返回值的传递方式:是用寄存器传还是使用内存传

什么是堆桟?

一个程序运行的时候,它的进程的地址空间一般可以分为四块:

代码区,数据区,堆,栈,每块功能如下:

区域

功能

代码区

存放函数被编译后的二进制可执行代码

数据区

只读区:存放常量,例如:常量字符串,const修饰的全局变量等可读写区:存放全局变量和静态变量

除去其他三个区域,剩下的都是堆,不连续

存放函数运行时所需的参数,寄存器环境,返回值,局部变量

以下面代码为例:

int TestFunction(char szBuff[],int nSize)

{

for (int iIndex = 0; iIndex < nSize; iIndex++)

{

szBuff[iIndex] = 'x';

}

return 3;

}

int main()

{

char szBuff[32] = { "sfjdlskfjl" };

int nRet = TestFunction(szBuff, 32);

return 0;

}

函数参数参数传递:

从上图中可以看出函数参数入栈

保存返回地址(紧挨着被调用函数的下一行可执行代码的内存地址)

从上图中可以看出函数调用完成后,紧挨着的第一条指令为:

00EB175B add esp,8

所以,参数传递完成后就是返回值入栈:

程序流程转移到被调用函数地址处

保存调用方栈底

切换到当前函数(被调用函数)的栈底

调用方栈底保存完成后,当前的栈顶(ESP记录的地址)就成为被调用函数的栈底

为局部变量分配空间

这里程序为调试版本,所以为局部变量分配的空间比较大,在Release版本中

会根据局部变量实际所需空间来分配大小

保存寄存器环境

这里一共保存了3个寄存器,共12字节,在Release版本下,只保存两个

在Debug版程序中(有/Zi(带有调试信息)和/Od(禁止优化)编译命令),除了为

局部变量分配较大的内存空间外,还会将分配的局部变量空间全部置为0xCC:

这种填充方式比较直观,能够让我们在调试时直观的观察到是否发生越界等错误

开始执行函数体代码

此时当前函数的栈的内存布局如下:

恢复寄存器环境

释放分配的局部变量空间

只是将当前的栈底指针(EBP)的值赋值给栈顶指针(ESP)就完成了:

恢复调用方栈底

平栈或者返回

如果是_fastcall,_stdcall调用约定,那么被调用函数平栈后,取出返回地址

函数流程转移到调用方

其他调用约定则是直接取出保存的返回地址,函数流程返回到调用方,又调用方

平栈

Copyright © 2022 星辰幻想游戏活动专区 All Rights Reserved.