C语言的函数调用过程

article/2025/9/1 12:59:17

C语言的函数调用过程

先上一段代码

#include<stdio.h>
int Add(int x, int y)
{int z = 0;z = x + y;return z;
}
#include <stdio.h>
int main()
{int a = 10;int b = 20;int c = Add(a, b);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这个函数计算两个数的值。接下来我们通过反汇编,来看看函数被后的过程。

反汇编代码

#include <stdio.h>
int main()
{
01151720  push        ebp  
01151721  mov         ebp,esp  
01151723  sub         esp,0E4h  
01151729  push        ebx  
0115172A  push        esi  
0115172B  push        edi  
0115172C  lea         edi,[ebp-0E4h]  
01151732  mov         ecx,39h  
01151737  mov         eax,0CCCCCCCCh  
0115173C  rep stos    dword ptr es:[edi]  int a = 10;
0115173E  mov         dword ptr [a],0Ah  int b = 20;
01151745  mov         dword ptr [b],14h  int c = Add(a, b);
0115174C  mov         eax,dword ptr [b]  
0115174F  push        eax  
01151750  mov         ecx,dword ptr [a]  
01151753  push        ecx  
01151754  call        _Add (01151104h)  
01151759  add         esp,8  
0115175C  mov         dword ptr [c],eax  return 0;
0115175F  xor         eax,eax  
}
01151761  pop         edi  
01151762  pop         esi  
01151763  pop         ebx  
01151764  add         esp,0E4h  
0115176A  cmp         ebp,esp  
0115176C  call        __RTC_CheckEsp (01151122h)  
01151771  mov         esp,ebp  
01151773  pop         ebp  
}
//Add()
#include<stdio.h>
int Add(int x, int y)
{
001016D0  push        ebp  
001016D1  mov         ebp,esp  
001016D3  sub         esp,0CCh  
001016D9  push        ebx  
001016DA  push        esi  
001016DB  push        edi  
001016DC  lea         edi,[ebp-0CCh]  
001016E2  mov         ecx,33h  
001016E7  mov         eax,0CCCCCCCCh  
001016EC  rep stos    dword ptr es:[edi]  int z = 0;
001016EE  mov         dword ptr [z],0  z = x + y;
001016F5  mov         eax,dword ptr [x]  
001016F8  add         eax,dword ptr [y]  
001016FB  mov         dword ptr [z],eax  return z;
001016FE  mov         eax,dword ptr [z]  
}
00101701  pop         edi  
00101702  pop         esi  
00101703  pop         ebx  
00101704  mov         esp,ebp  
00101706  pop         ebp  
00101707  ret  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

以上就是反汇编代码。现在我们一段一段的进行解读,去看看整个程序是怎么执行的。

main()函数的创建

01151720  push        ebp  
01151721  mov         ebp,esp  
01151723  sub         esp,0E4h  
01151729  push        ebx  
0115172A  push        esi  
0115172B  push        edi  
0115172C  lea         edi,[ebp-0E4h]  
01151732  mov         ecx,39h  
01151737  mov         eax,0CCCCCCCCh  
0115173C  rep stos    dword ptr es:[edi]  
//分割线.........................
int a = 10;
0115173E  mov         dword ptr [a],0Ah  int b = 20;
01151745  mov         dword ptr [b],14h 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这里要给读者朋友们简单说一下变量的含义。ebp 和 esp 是两个通用寄存器。它们可以存储 立即数 和 内存地址。esi是源变址寄存器。edi是目的变址寄存器。 
它们都可用来储存地址。

在main()函数之前有一个函数CRTStartUp,ebp为这个函数的栈底,esp为这个函数的栈顶。 
这里写图片描述 
接下来通过前两句代码mov 赋值移动,它们的状态变成了如下: 
这里写图片描述 
第三句话中,sub是减法指令,这句话使得esp向后移动了0E4h个单位。 
接下来的三句话,是将ebx,esi,edi压入了栈当中。 
这里写图片描述 
第七句话,lea 的意思是load effective address,也就是将[ebp - 0E4h]的地址取出来,赋值给了edi。 
第八句话,将39h赋值移动到ecx。第九句话,将内容赋值移动给eax。 
第十句话,rep stos 是重复拷贝数据。 
注意: 
ecx寄存器是用来保存复制粘贴的次数,ecx是计数器。 
eax寄存器是用来传送信息的,eax是累加器,通常用来存储数据。 
rep 是重复上述指令。 
stos 是将eax的值赋值移动给 es:[edi]这个地址的存储单元。 
这里写图片描述 
现在其中的空白部分被cc cc cc cc充满了。这里cc cc就是经常遇到的“烫烫”。 
分割线 
接下来的操作很好理解,其目的是将 a和b创建,并且压入栈中。 
这里写图片描述

Add()函数的调用过程

    int c = Add(a, b);
0115174C  mov         eax,dword ptr [b]  
0115174F  push        eax  
01151750  mov         ecx,dword ptr [a]  
01151753  push        ecx  
01151754  call        _Add (01151104h)  
01151759  add         esp,8  
0115175C  mov         dword ptr [c],eax  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

前四句话,它将a和b找了个寄存器存起来,与此同时,以函数参数列表倒着的方式压入栈中。 
这里写图片描述 
方便起见,下面的图将只截取上半部分。

接下来使用了call指令,这个指令是先将 指令 移动到下一句话,然后再操纵call 后面的内容。在call处按F11我们将跳转到Add()函数。 
这里写图片描述 
Add()函数


#include<stdio.h>
int Add(int x, int y)
{
001016D0  push        ebp  
001016D1  mov         ebp,esp  
001016D3  sub         esp,0CCh  
001016D9  push        ebx  
001016DA  push        esi  
001016DB  push        edi  
001016DC  lea         edi,[ebp-0CCh]  
001016E2  mov         ecx,33h  
001016E7  mov         eax,0CCCCCCCCh  
001016EC  rep stos    dword ptr es:[edi]  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这一部分和main函数创建过程非常相似。所以用俩图来表示。 
这里写图片描述 
这里写图片描述

    int z = 0;
001016EE  mov         dword ptr [z],0  z = x + y;
001016F5  mov         eax,dword ptr [x]  
001016F8  add         eax,dword ptr [y]  
001016FB  mov         dword ptr [z],eax  return z;
001016FE  mov         eax,dword ptr [z]  
}
00101701  pop         edi  
00101702  pop         esi  
00101703  pop         ebx  
00101704  mov         esp,ebp  
00101706  pop         ebp  
00101707  ret  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这段代码非常有意思,请读者朋友们一定要注意。 
首先,我们创建了变量 ptr[z]。然后,将ptr[x]的值放在eax里边,将ptr[y]的值与eax相加后再放在eax里,将eax的值赋值移动到 ptr[z]中。最后,我们需要return z。这个地方编译器再次将ptr[z]的值赋值移动到了eax。这么做的原因是:ptr[x]、ptr[y]、ptr[z]将在函数完成后就会销毁,但是寄存器eax不会被销毁,所以在eax的值非常安全。

括号外的第四句话非常重要。它将销毁该函数的所有数据。 
这里写图片描述 
将ebp弹出后,该点只有一个指针esp。接下来执行ret,即 return 返回。返回到我们刚才call下面的地方。

main()函数的销毁

01151759  add         esp,8  
0115175C  mov         dword ptr [c],eax  return 0;
0115175F  xor         eax,eax  
}
01151761  pop         edi  
01151762  pop         esi  
01151763  pop         ebx  
01151764  add         esp,0E4h  
0115176A  cmp         ebp,esp  
0115176C  call        __RTC_CheckEsp (01151122h)  
01151771  mov         esp,ebp  
01151773  pop         ebp  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

现在我们跳回到了该段的第一句话,现在esp + 8向高地址进发了。其目的就是消去了形参实例化的a,b。第二句话,我们将eax里保存的数据赋值移动给了c。最后,xor异或,自己和自己异或 其值变为0。

这里写图片描述 
之后,我们出栈了 edi,esi,ebx。现在esp指向了最开始我们红色那个箭头 ebp - 0E4h。 
然后,由于add操作,我们继续向高地址进发,到了我们最开始的地方。 
这里写图片描述 
这里写图片描述 
现在,我们到了cmp指令,cmp是比较指令。它的操作是拿第一个数减去第二个数,如果相减为ZF=0说明相等。但是cmp并不会真的减。到此位置main()函数就真的执行完毕,然后esp,ebp。回到了原来的位置。至于后面的函数调用,并不需要深究。至此我们就看到了在汇编上函数如何实现的


http://chatgpt.dhexx.cn/article/GUcuS0bY.shtml

相关文章

C--函数调用

C函数的调用约定 编译器实现函数调用时所遵循的一系列规则称为函数的“调用约定&#xff08;Calling Convention&#xff09;” 对于C语言来说&#xff0c;运行在X86-64平台上的编译器基本都会根据操作系统的不同选择使用几种常见的调用约定。例如&#xff0c;在wiindows下通常…

C语言 函数调用的过程

例题&#xff1a;求两个整数中的较小者&#xff0c;用函数调用实现。 【代码实现】 int Min(int x, int y) {if (x < y){return x;}elsereturn y; } int main() {int Min(int x, int y);int a, b, c;printf("输入两个要比较的整数&#xff1a;\n");scanf("%…

函数调用的过程分析

一、函数调用机制 局部变量占用的内存是在程序执行过程中”动态”地建立和释放的。这种”动态”是通过栈由系统自动管理进行的。当任何一个函数调用发生时,系统都要作以下工作: 1)建立栈帧空间;2)保护现场: 主调函数运行状态和返回地址入栈&#xff1b;3)为被调函数传递数据(…

C/C++ 函数调用是如何实现的?

一、写在前面的话 C/C 函数调用方式与栈原理是 C/C 开发必须要掌握的基础知识&#xff0c;也是高级技术岗位面试中高频题。我真的真的真的建议无论是使用 C/C 的学生还是广大 C/C 开发者&#xff0c;都该掌握此回答中所介绍的知识。 如果你看不懂接下来第二部分在说什么&#…

函数调用过程

今天突然看到有人私信我说一直没写函数调用过程&#xff08;栈帧的形成和销毁过程&#xff09;这篇博文&#xff0c;赶紧补上。 刚看的栈帧内容时&#xff0c;我很迷惑&#xff0c;我觉得栈帧创建和销毁很麻烦&#xff0c;几句话根本说不完&#xff0c;而且我好像描述不清楚他…

浅谈函数调用!

导语 | 在任意一门编程语言中&#xff0c;函数调用基本上都是非常常见的操作&#xff1b;我们都知道&#xff0c;函数是由调用栈实现的&#xff0c;不同的函数调用会切换上下文&#xff1b;但是&#xff0c;你是否好奇&#xff0c;对于一个函数调用而言&#xff0c;其底层到底…

函数调用流程

函数调用模型 1. 函数调用流程函数调用流程分析函数参数调用代码分析自右向左入栈顺序的优点 2. 调用惯例函数参数的传递顺序和方式栈的维护方式调用管理表 3. 函数变量传递分析分析图 1. 函数调用流程 栈(stack)是现代计算机程序里最为重要的概念之一&#xff0c;几乎每一个程…

函数调用栈

函数调用栈 我们在编程中写的函数&#xff0c;会被编译器编译为机器指令&#xff0c;写入可执行文件&#xff0c;程序执行的时候&#xff0c;会把这个可执行文件加载到内存&#xff0c;在虚拟地址空间中的代码段存放。 如果在一个函数中调用另一个函数&#xff0c;编译器就会…

函数调用和使用

1.函数是什么 函数&#xff08;Function&#xff09;能实现的功能从简单到复杂&#xff0c;各式各样&#xff0c;但其本质是相通的&#xff1a;“喂”给函数一些数据&#xff0c;它就能内部消化&#xff0c;给你“吐”出你想要的东西。 2.定义和调用函数 2.1 定义函数 #…

C语言——函数的调用

函数的调用 传值调用 函数的形参和实参分别占有不同的内存块&#xff0c;对形参的修改不会影响实参。 传址调用 1.传址调用是把函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式 2.这种传参方式可以让函数和函数外边的变量建立起真正的联系&#xff0c;也就…

C语言之函数调用

C语言之函数调用 “温故而知新&#xff0c;可以为师矣”&#xff01; 让我们开启函数的道路吧&#xff01; 今天主要讲函数的调用方式&#xff01; 在讲之前&#xff0c;先回顾一下实际参数和形式参数的区别&#xff1b; 1.在定义函数时函数名后面括号中的变量名称为“形式参数…

C语言函数的调用

函数调用&#xff08;Function Call&#xff09;&#xff0c;就是使用已经定义好的函数。函数调用的一般形式为&#xff1a; functionName(param1, param2, param3 ...);functionName 是函数名称&#xff0c;param1, param2, param3 …是实参列表。实参可以是常数、变量、表达…

Windows编程-001

如果建立的是Win32控制台工程&#xff08;入口函数是main函数&#xff09;的话&#xff0c;WinMain函数不能作为入口函数&#xff0c;如果想要解决这个问题的话&#xff0c;可以打开项目属性->链接器->系统->子系统&#xff0c;把子系统对应的“控制台”改为“窗口”。…

windows全系1

windows操作系统专贴(一定有你想要的) 2006年12月01日 22:25 windows 98 简体中文零售版第三版 语言&#xff1a;简体中文 类型&#xff1a;操作系统 大小&#xff1a;180MB 环境&#xff1a;9x/Me/NT/2000/XP/.Net/ 授权&#xff1a;零售版 软件介绍:这个版本是SE的改进版,比前…

对不起,说句粗话——这个太屌了,windows1.0安装程序(附下载)

今天逛一个软件论坛发现的&#xff0c;只有几百K。遥想当今我刚接触windows的版本是3.1&#xff0c;当时记得很清楚哦&#xff0c;进入windows要从dos命令行进入。现在一转眼&#xff0c;变成进入伪dos是运行栏里敲cmd了。 唉&#xff0c;想当年尿尿还能疵2米多远的时候&#…

Windows入门(一)

本人正在学习Windows编程操作&#xff0c;所以进行一些记录&#xff0c;希望对刚入门的个位有所帮助。 目录 1.什么是win32编程 2. 一个简单的win32程序 2.1 创建一个空项目 2.2 入口函数 2.3 注册窗口 2.3.1 窗口回调函数 2.4 创建窗口 2.5 显示窗口 2.6 更新窗口 3.…

win11网页版

网页版地址 点它 https://win11.blueedge.me/ 其github库地址其github库地址https://github.com/blueedgetechno/win11React/

windows10/11子系统安装ubuntu22.04

学习目标&#xff1a; winfows10/11 安装wsl内核 基于wsl内核安装Ubuntu系统 基于ubuntu系统安装docker环境 学习内容&#xff1a; 系统下安装wsl2下载ubuntu安装包windows11 安装ubuntu 22.04系统ubuntu 22.04 安装dockerdocker 启动、测试 windows下安装wsl2内核 1、如果未…

快速教你在虚拟机上完美安装Windows1.0

想必不用我说大家用虚拟机安装Windows1.0时都是这样的: 今天我就来教大家如何正确安装Windows1.0 因为在1985年(Windows1.0诞生的年代)的鼠标驱动已经不匹配我们的电脑了,所以我们需要先解决鼠标这个问题。 第一步: 打开UltraISO,在里面打开我们Windows1.0第一个镜像(…

Windows:

服务主机:本地系统(网络受限) CPU利用率高&#xff0c;磁盘利用率高 解决方法&#xff1a; 方法一&#xff1a;禁用SuperFetch服务 计算机&#xff1a;—右键“管理”—SuperFetch—停止。或属性—-禁用。 开机就占用50%的内存&#xff08;共8G&#xff09; 关闭家庭组  家…