C++基础 函数
来源:互联网 发布:洛克王国手机辅助软件 编辑:程序博客网 时间:2024/04/29 22:27
C++函数
- C函数
- 第1章 引言
- 第2章 程序内存存储区
- 1 栈区
- 2 堆区
- 3 全局区
- 4 常量区
- 5 代码区
- 第3章 函数
- 1 函数的基本流程
- 2 函数的临时变量
- 3 函数的传入参数
- 4 函数的返回
- 第4章 函数的栈空间
- 第5章 小结
第1章 引言
函数作为执行语句的集合,在程序语言中发挥着重要作用。
本文将介绍函数的基本处理流程、传入参数、临时变量和返回机制来解释C++的函数基础。在程序运行过程中,通过监控内存的变化,来解释运行时态时的函数行为。并从普通函数延伸到类函数,介绍C++的多态特性的实现基础。
在解释基础语法时,本文会运用到较多的汇编语言(VS2010 Debug环境)。
第2章 程序内存存储区
内存存储区用于存放程序的数据以及执行代码。C++的内存存储区分为以下5种。
2.1. 栈区
栈区在程序运行时编译器自动分配,用于存放函数的参数、临时变量、函数栈指针、函数返回地址等。栈区,由编译器负责维护,程序结束时,由编译器释放。
2.2. 堆区
堆区在程序运行时,由代码分配释放。
2.3. 全局区
全局区由编译器维护,存放未初始化的全局变量和静态变量
2.4. 常量区
顾名思义用于存放程序中的常量。
2.5. 代码区
存放函数体的二进制代码。
其中,以上几个存储区,只有堆是程序员可以直接操作分配的。
第3章 函数
3.1. 函数的基本流程
函数的基本流程分为以下6步:
1、保存调用前状态,主要是保存了函数栈指针ebp,ebp是什么后面再仔细介绍,主要是用来为函数的栈空间分边界的。
除了ebp,函数还保存了这三个寄存器ebx、esi和edi。
2、分配临时空间,程序为函数中的临时变量分配默认大小(0xC0)的临时空间。
3、为第二步刚分配的临时空间赋上初始值0x CCCCCCCCh。当代码中一些临时变量没有赋初值的时候,默认的值就会采用这个初始值。
4、程序员的代码,这里开始执行程序员在这个函数写的代码。如果是一个空函数,这一步是没有执行指令的。
5、恢复调用前的状态,恢复几个寄存器ebx、esi、edi和两个函数栈指针ebp和esp。这里的esp就是栈顶指针,是相对于ebp栈底指针来说的。
6、返回调用处代码。
为了说明上面六个基本处理流程,我们这里选取一个空函数的汇编指令来进行实例说明:
函数原型:void fun(){}。调用:fun();
fun();在编译时,会使用call fun address替换,这里fun();会变成如下代码:
01302CFE call fun (13011D1h)0x13011D1h处的代码如下013011D1 jmp fun1 (13017C0h)
接着前往位置0x13017C0h处,便是函数fun代码段。为了方便的说明每一行的指令,说明通过程序代码的注释方式,跟在每一句指令后面。
void fun(){013017C0 push ebp //第一步:保存ebp ebp和esp是一对栈指针//其中ebp是栈底指针,esp是栈顶指针013017C1 mov ebp,esp //ebp = esp013017C3 sub esp,0C0h //第二步:在栈上分配0xC0大小的临时空间013017C9 push ebx //保存ebx013017CA push esi //保存esi013017CB push edi //保存edi013017CC lea edi,[ebp-0C0h] //读入[ebp - 0c0h]的有效地址;//就是刚分配013017D2 mov ecx,30h //第三步0x30改ecx,就是下面的循环次数013017D7 mov eax,0CCCCCCCCh //将0CCCCCCCCh赋给eax 013017DC rep stos dword ptr es:[edi] // rep便是重复执行,每执行一次ecx便减小1,知道为0停止循环//stos 将eax的值拷贝到es:[edi]所指向的地址//最后就是填满了临时存储区}013017DE pop edi //第五步,返回edi013017DF pop esi //返回esi013017E0 pop ebx //返回ebx013017E1 mov esp,ebp //将此函数的ebp赋给esp,恢复至调用前的状态013017E3 pop ebp //重新获得调用此函数前的ebp013017E4 ret //第六步,返回
3.2. 函数的临时变量
众所周知,函数的临时变量存放在栈。这是每一个程序员都知道一个常识,那么这些变量是在栈中如何存放的?这里我们会针对临时变量来介绍C++处理临时变量的方法。
针对3.1中的简单函数,进行如下改编:
函数 void fun2(){int i = 0;}。直接展开汇编指令,为了不赘述,这里我会去除函数处理的基本流程指令,用…代替。
void fun2(){int i = 0;012A180E mov dword ptr [i],0 //将0赋值给i所指向的地方,这个i的位置是ebp-4的位置}由于直接使用VS2010查看的汇编指令,无法清晰的说明临时变量的存放方式,我这边使用IDA反编译生成的汇编代码进行解释。.text:004117F0 ; void __cdecl fun2() //这里__cdecl是函数的调用方式//这里不做解释了.text:004117F0 ?fun2@@YAXXZ proc near ; CODE XREF: fun2(void)j.text:004117F0.text:004117F0 var_CC = byte ptr -0CCh //这里是栈临时区的大小,byte ptr是类型.text:004117F0 i = dword ptr -8 //i = -8 ,dowrd ptr是类型.text:004117F0….text:0041180E mov [ebp+i], 0 //ebp + i = ebp – 8 //其实这个变量的存放位置ebp和ebp- 0CCh之间.text:00411815 pop edi //与VS2010中类似,略.text:00411816 pop esi //与VS2010中类似,略…
从内存监控我们来看一下这个临时变量。为了方便
解了函数的临时变量后,我们进一步来了解函数的传入参数。调用者是如何将函数传入至函数,函数又是如何获取到这些参数的呢?
3.3. 函数的传入参数
了解了函数的临时变量后,我们进一步来了解函数的传入参数。调用者是如何将函数传入至函数,函数又是如何获取到这些参数的呢?
这里我们会针对这个问题,来展开讨论。
首先我们使用以下函数:
void fun3(int x){x += 1;}
调用:fun3(10);
首先先来看调用处的汇编指令。
012A2D08 push 0Ah //将参数0xA压入栈012A2D0A call fun3 (12A11CCh) //调用fun3012A2D0F add esp,4 //平衡栈,这句代码是否出现,还要看函数的调用方式
去除基本流程指令,展开函数段:
void fun3(int x){...x += 1;012A205E mov eax,dword ptr [x] //将参数x的值赋给eax//x的位置在ebp-4012A2061 add eax,1 //等价于eax = eax + 1012A2064 mov dword ptr [x],eax //等价于x = eax}....
由于与3.2章节中同样的原因,我们还是使用IDA中的代码再一次分析。
….text:00412040 x = dword ptr 8 ….text:0041205E mov eax, [ebp+x] .text:00412061 add eax, 1 .text:00412064 mov [ebp+x], eax …
我们发现,变量x处于ebp+8处,我们这里解释这些变量的存放位置,主要是对函数栈空间有一个认识。
3.4. 函数的返回
接下来就是函数的返回值的问题。一样的分析方法:
int fun5(int x,int y){int z = x+ y;return z;}
调用:
int z = fun5(30,40);
去除公共代码后展开 :
012A2D1E push 28h //压入参数0x28,就是fun5(30,40)中的40012A2D20 push 1Eh //压入参数0x1E,就是fun5(30,40)中的30 //这里参数压入栈的方式也是和调用方式有关, //这里C++使用的是__stdcall,压栈是从右往左012A2D22 call fun5 (12A11C7h) //调用fun5012A2D27 add esp,8 //平衡栈012A2D2A mov dword ptr [z],eax //函数的返回值在eax寄存中
看一下fun3内部的汇编。之后没有特殊说明,函数的公共汇编代码都会去掉用…替代
int fun5(int x,int y){...int z = x+ y;012A267E mov eax,dword ptr [x] //将参数x赋给eax012A2681 add eax,dword ptr [y] //将y和eax相加,相当于eax = eax + y012A2684 mov dword ptr [z],eax //相当于z = eaxreturn z;012A2687 mov eax,dword ptr [z] //将z赋给eax,函数的返回值是靠eax寄存器传递的}...
第4章 函数的栈空间
我们直接来看下面的栈空间图:
图1 栈空间图
函数栈中,我们ESP和EBP来划分函数的栈空间,EBP表示底,ESP表示顶部。这样函数的栈空间便是这两个指针之间区域。
这个区域主要是放函数的临时变量。
我们在EBP往下看,其实是内存的高位。EBP+0x4处存放的是Old EBP。其实也是比较好理解的,这个就是函数调用者的EBP。也就是说如果函数fun1中调用了fun2函数,那么这个地方保存的就是fun1的EBP。
再往下看,EBP+0x8的地方放的是一个返回地址,也就是函数调用完成之后,返回到调用处的代码,接着往下执行。
接着往下看,便是一些传入参数存放的位置。我们可以回忆,调用函数的时候,首先我们把参数压入栈,就是这个操作吧函数的传入参数放在了这个位置。
对于此图会在另一篇文章中介绍它的由来。
第5章 小结
关于函数,其实还有函数的调用方式以及类函数等,限于篇幅,这里不再介绍,C++基础系列的文章中在进行介绍。
这一篇是对C++基础系列第一篇也是之后文章的基础。今后的文章都会以此篇为基础,详细讲解C++的特性原理。
- C 基础 (函数)
- C基础:函数
- C语言基础 函数
- C语言基础 函数
- C函数指针基础
- 【基础C&C++】内存函数
- C语言基础函数(一)
- C语言基础 函数指针
- C++---基础篇(函数)
- C语言函数指针基础
- C语言函数指针基础
- C语言函数指针基础
- C语言函数指针基础
- C语言函数指针基础
- C语言函数指针基础
- C语言函数指针基础
- C语言函数指针基础
- C语言函数指针基础
- 设计模式总结
- 创建一个直角三角形类(regular triangle)RTriangle类,实现下列接口IShape。两条直角边长作为RTriangle类的私有成员,类中包含参数为直角边的构造方法。
- tomcat org.apache.catalina.LifecycleException: Failed to start component
- Unity优化之ScrollRect
- 深度学习2016主要进展
- C++基础 函数
- 在Ubuntu上安装Python版MXNet
- WebViewJavascriptBridge初识
- FastDFS搭建及java整合代码
- 代码调试
- Oracle基本语法查询语句
- Android中的一个小特效,从圆变成纸飞机
- HDU 5996 dingyeye loves stone(Nim 变形)
- Leetcode 412 Fizz Buzz