系统学习-C++内存分配

C++内存分配是一个很基础的问题,明白这个分配机制,有很多C++的问题都可以很容易理解。比如const成员变量为何需要利用构造函数初始化列表才能进行初始化;static关键字为什么可以改变存储属性;new/malloc的内存分配方式等。

程序结构理解

这是描述32位系统下程序大致内存结构的经典老图(64位类似,只是32位的图网上有现成的),具体就不赘述了。
内存分配

stack:函数栈区
heap:函数堆区
.bss:用来存放程序中未初始化的全局变量和静态变量
.data:数据段-静态存储区
.txt:代码段

程序运行过程

通过CS:IP两个寄存器一条一条的确定执行的指令地址,并依次执行指令。
基本流程:CS+IP->地址(地址总线)->取指令(数据总线)->执行
具体可以参考:X86处理器中的CS与IP寄存器

Stack区

主要是函数调用栈
函数调用栈的经典老图

说明一下在x86_64架构下,当寄存器足够存放参数时,是不会对参数进行压栈的,因此图中参数1到n(对应函数参数列表是从右到左)是可选的,当把上个栈帧的基址压入栈中时,新的栈帧就开始了。
  不同栈之间主要通过EBP与ESP两个寄存器来维护,具体可参考EBP与ESP讲解
  相信开发同学们对于函数调用栈的结构早就清楚了,但是有没有想过为什么c/cpp编写的程序函数调用栈长这样?其实没有为什么,只是因为gcc编译器是这么工作的,这是gcc为函数调用设计的规范(更合理的说法应该是编写gcc的大佬们),不过其设计背后的原因其实也不难想到:一是因为各个函数的指令集在物理空间上是独立的,自然需要处理指令的跳转;二是需要解决输入和输出的传递,为什么输入参数少的时候直接用寄存器呢?当然是因为CPU访问寄存器更快,可惜寄存器个数有限,不然我们就不需要缓存和内存了(寄存器也是一片存储空间,不同的寄存器名称只是对不同的地址块的引用而已)。
  将寄存器中的变量拷贝到内存的原理:通过Mov指令
  寄存器向内存
  也就是说gcc帮我们把c/cpp等高级语言编写的代码,按照规范转化为了汇编指令。

反汇编分析

源码

#include <iostream>
using namespace std;
int add1(int num1, int num2,int num3)
{
	int a = 100;
	return a+num1+num2+num3;
}
template <typename T>
T add2(T a, T b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int b = 15;
	int c = 21;
	int d = add1(a, b, c);
	int t = 100;
	int f = add2(d, t);
	cout << f << endl;
	cin.get();
}

反汇编

int main()
{
002F8F60  push        ebp  								    //通过ebp和esp来控制栈的界限
002F8F61  mov         ebp,esp                               //将esp的值赋值给ebp,esp开始增长
002F8F63  sub         esp,108h  						    //是从高地址向低地址走的
002F8F69  push        ebx  
002F8F6A  push        esi  
002F8F6B  push        edi  
002F8F6C  lea         edi,[ebp-108h]  
002F8F72  mov         ecx,42h  
002F8F77  mov         eax,0CCCCCCCCh  
002F8F7C  rep stos    dword ptr es:[edi]  
	int a = 10;
002F8F7E  mov         dword ptr [a],0Ah 				    //这是一个赋值的过程 
	int b = 15;
002F8F85  mov         dword ptr [b],0Fh  				    //ptr就是内存的一个地址,现在将参数赋值到了内存上
	int c = 21;
002F8F8C  mov         dword ptr [c],15h  
	int d = add1(a, b, c);
002F8F93  mov         eax,dword ptr [c]  					//在函数调用时,是将参数按照从后往前的顺序依次赋值的
002F8F96  push        eax  
002F8F97  mov         ecx,dword ptr [b]  					//顺序是c,b,a
002F8F9A  push        ecx  
002F8F9B  mov         edx,dword ptr [a]  
002F8F9E  push        edx  								    //eax、ebx、ecx、edx为变量寄存器
002F8F9F  call        add1 (02EEA36h)                       //调用函数  
002F8FA4  add         esp,0Ch  
002F8FA7  mov         dword ptr [d],eax  					//函数返回值是通过eax传回来的,将值从寄存器中赋值到了内存上
	int t = 100;
002F8FAA  mov         dword ptr [t],64h  
	int f = add2(d, t);
002F8FB1  mov         eax,dword ptr [t]  
002F8FB4  push        eax  
002F8FB5  mov         ecx,dword ptr [d]  
002F8FB8  push        ecx  
002F8FB9  call        add2<int> (02EEA3Bh)  
002F8FBE  add         esp,8  
002F8FC1  mov         dword ptr [f],eax 					//函数返回值是通过eax传回来的,将值从寄存器中赋值到了内存上
	cout << f << endl;
002F8FC4  mov         esi,esp  
002F8FC6  push        offset std::endl<char,std::char_traits<char> > (02ED48Dh)  
002F8FCB  mov         edi,esp  
002F8FCD  mov         eax,dword ptr [f]  
002F8FD0  push        eax  
002F8FD1  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0339140h)]  
002F8FD7  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (033910Ch)]  
002F8FDD  cmp         edi,esp  
002F8FDF  call        __RTC_CheckEsp (02EDA5Ah)  
002F8FE4  mov         ecx,eax  
002F8FE6  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0339108h)]  
002F8FEC  cmp         esi,esp  
002F8FEE  call        __RTC_CheckEsp (02EDA5Ah)  
	cin.get();
002F8FF3  mov         esi,esp  
002F8FF5  mov         ecx,dword ptr [_imp_?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@A (033914Ch)]  
002F8FFB  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::get (0339148h)]  
002F9001  cmp         esi,esp  
002F9003  call        __RTC_CheckEsp (02EDA5Ah)  
}
002F9008  xor         eax,eax  
002F900A  pop         edi  									//退栈的一个过程
002F900B  pop         esi  
002F900C  pop         ebx  
002F900D  add         esp,108h  
002F9013  cmp         ebp,esp  
002F9015  call        __RTC_CheckEsp (02EDA5Ah)  
002F901A  mov         esp,ebp  
002F901C  pop         ebp  
002F901D  ret  												//返回标志位

Visual Studio中反汇编:设置断点,调试,之后点击调试->窗口->反汇编即可。
CodeBlocks中反汇编:设置断点,调试,之后点击debug->debugging windows->disassembly即可。

总结

编译器将程序代码编译成二进制文件,CS:IP指导一条条指令按序从.txt执行。在执行的过程中,程序结构如上图,其中函数Stack由EBP与ESP维护,动态申请的内存在Heap上开辟空间,静态变量与全局变量在.data段,未初始化的全局变量和静态变量在.bss段。

猜你喜欢

转载自blog.csdn.net/BigBrick/article/details/85312998
今日推荐