C语言函数调用完整过程

C语言函数调用详细过程

函数调用是步骤如下:

  1. 按照调用约定传参

    • 调用约定是调用方(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;
        }

    函数参数参数传递:

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

  2. 保存返回地址(紧挨着被调用函数的下一行可执行代码的内存地址)
    从上图中可以看出函数调用完成后,紧挨着的第一条指令为:
    00EB175B add esp,8
    所以,参数传递完成后就是返回值入栈:
  3. 程序流程转移到被调用函数地址处
  4. 保存调用方栈底
  5. 切换到当前函数(被调用函数)的栈底
    调用方栈底保存完成后,当前的栈顶(ESP记录的地址)就成为被调用函数的栈底
  6. 为局部变量分配空间

    这里程序为调试版本,所以为局部变量分配的空间比较大,在Release版本中
    会根据局部变量实际所需空间来分配大小
  7. 保存寄存器环境
    这里一共保存了3个寄存器,共12字节,在Release版本下,只保存两个

    在Debug版程序中(有/Zi(带有调试信息)和/Od(禁止优化)编译命令),除了为
    局部变量分配较大的内存空间外,还会将分配的局部变量空间全部置为0xCC:

    这种填充方式比较直观,能够让我们在调试时直观的观察到是否发生越界等错误
  8. 开始执行函数体代码
    此时当前函数的栈的内存布局如下:
  9. 恢复寄存器环境
  10. 释放分配的局部变量空间
    只是将当前的栈底指针(EBP)的值赋值给栈顶指针(ESP)就完成了:
  11. 恢复调用方栈底
  12. 平栈或者返回
    • 如果是_fastcall,_stdcall调用约定,那么被调用函数平栈后,取出返回地址
      函数流程转移到调用方
    • 其他调用约定则是直接取出保存的返回地址,函数流程返回到调用方,又调用方
      平栈

原文地址:https://www.cnblogs.com/UnknowCodeMaker/p/11002225.html

时间: 2024-10-10 13:20:39

C语言函数调用完整过程的相关文章

C语言函数调用栈(三)

6 调用栈实例分析 本节通过代码实例分析函数调用过程中栈帧的布局.形成和消亡. 6.1 栈帧的布局 示例代码如下: 1 //StackReg.c 2 #include <stdio.h> 3 4 //获取函数运行时寄存器%ebp和%esp的值 5 #define FETCH_SREG(_ebp, _esp) do{ 6 asm volatile( 7 "movl %%ebp, %0 \n" 8 "movl %%esp, %1 \n" 9 : "

Ubuntu上snmp安装、配置、启动及远程测试完整过程

0.说明 关于一个完整的教程,还是那句话,国内的要么不完整,要么就太旧了,而且思路也不清晰,所以这里写一篇完整的给大家分享一下. 虽然对于Linux主机的监控可以通过执行特定的命令来完成,但是相比之后,通过snmp的方式来获取Linux主机的信息则会更轻松简单些,只不过在使用前的配置可能需要花多一点时间,不过这绝对值得!而且如果需要开发Linux主机的监控软件,那使用snmp肯定是首选,毕竟它可以获得的信息太多太多! 后面的内容就来分享一下在Ubuntu上安装.配置.启动snmp以及进行远程测试

41-50(UIApplication和delegate,UIApplicationMain,UIWindow,程序启动的完整过程,控制器view的延迟加载)

41.UIApplication和delegate 42.UIPickerView 43.UIDatePicker 44.程序启动的完整过程 45.UIApplicationMain 46.UIWindow 47.如何创建一个控制器 48.控制器view的延迟加载 49.多控制器 50.UINavigationController的使用步骤 { 这几天一直在赶项目, 今天终于闲下来了! 今天是个好日子,空间里满天的2014520 那么来看看我们程序员的爱情吧! 爱情就是死循环,一旦执行就陷进去了

UIPickerView/UIDatePicker/程序启动的完整过程

一.UIPickerView 1.UIPickerView的常见属性 数据源(用来告诉UIPickerView有多少列多少行) @property(nonatomic,assign) id<UIPickerViewDataSource> dataSource; 代理(用来告诉UIPickerView每1列的每1行显示什么内容,监听UIPickerView的选择) @property(nonatomic,assign) id<UIPickerViewDelegate>   deleg

测试c语言函数调用性能因素之测试二

函数调用:即调用函数调用被调用函数,调用函数压栈,被调用函数执行,调用函数出栈,调用函数继续执行的一个看似简单的过程,系统底层却做了大量操作. 操作: 1,               调用函数帧指针(函数参数,局部变量,栈帧状态值,函数返回地址)入栈,栈指针自减 2,               保存调用函数的状态数据入寄存器 3,               被调用函数帧指针入栈,执行当前的被调用函数 4,               被调用函数执行结束,退栈,返回到调用函数的帧指针,从寄存

UIPickerView,程序启动的完整过程

一.UIPickerView1.UIPickerView的常见属性// 数据源(用来告诉UIPickerView有多少列多少行)@property(nonatomic,assign) id<UIPickerViewDataSource> dataSource;// 代理(用来告诉UIPickerView每1列的每1行显示什么内容,监听UIPickerView的选择)@property(nonatomic,assign) id<UIPickerViewDelegate>   dele

测试c语言函数调用性能因素

函数调用:即调用函数调用被调用函数,调用函数压栈,被调用函数执行,调用函数出栈,调用函数继续执行的一个看似简单的过程,系统底层却做了大量操作. 操作: 1,               调用函数帧指针(函数参数,局部变量,栈帧状态值,函数返回地址)入栈,栈指针自减 2,               保存调用函数的状态数据入寄存器 3,               被调用函数帧指针入栈,执行当前的被调用函数 4,               被调用函数执行结束,退栈,返回到调用函数的帧指针,从寄存

测试c语言函数调用性能因素之测试三

函数调用:即调用函数调用被调用函数,调用函数压栈,被调用函数执行,调用函数出栈,调用函数继续执行的一个看似简单的过程,系统底层却做了大量操作. 操作: 1,               调用函数帧指针(函数参数,局部变量,栈帧状态值,函数返回地址)入栈,栈指针自减 2,               保存调用函数的状态数据入寄存器 3,               被调用函数帧指针入栈,执行当前的被调用函数 4,               被调用函数执行结束,退栈,返回到调用函数的帧指针,从寄存

C语言的编译过程、安装gcc编译器以及设置环境变量

以我对C语言编译过程的了解,我用了一点时间画了一个图,提供给大家参考一下,希望有些能对您的问题提上帮助. 前几天刚初步学习了C语言的编译过程,感触挺深的.在C语言中头文件其实起了一个很大的作用. 1.头文件可以不需要编译 2.可以查看具体的声明 3.头文件加上实现文件的o文件提交给使用者即可 ,不需要知道源代码 4..o文件预先编译,所以整个项目编译时,会大大提高编译的时间 . 5.当一个文件(A.c文件)依赖于头文件(b.h)时 ,如果b.c编译之后形成的b.o文件重新编译后,a.o的文件不需