高级语言隐藏了太多信息。这一篇用汇编来直观探究 C/C++ 变量,函数,类,继承等等再内存中是怎么表示的
先来看这样一段代码,分为三个部分。 先准备好三个部分准备反汇编。
1. defineVar() 函数内解释 变量在内存中的表现形式
2. varPtr() 函数内解释指针与引用在内存中的表现形式
3.constdefine() 函数内解释const与define的区别跟在内存中的表现形式
#include<cstdio>
//define与const在内存中的形式
void constdefine()
{
#define PI 3
const int a = 5;
const int* const bs = &a;
int P = PI;
}
//指针与引用在内存中的表现形式
void varPtr()
{
int a = 0x0F;
int * ptr = &a;
char * pt = (char*)ptr;
int & sbs = a;
}
//各变量在内存中的表现形式
void defineVar()
{
int a = 4;
int b = 5;
int c = 6;
float abc = 4.5f;
double lop = 8.9;
}
int main()
{
defineVar();
varPtr();
constdefine();
return 0;
}
接下来直接OD(当然也可以选择VS的反汇编窗口),我这里选择了OD。可以忽略这张图,直接看下面(给出了剪切反汇编代码)
先看第一部分(函数体内,已经粘贴出来):
解释在反汇编代码中,首先看defineVar()函数
//推入栈中,保存EBP的值
004013ED PUSH EBP
//用EBP保存ESP的调用初值
004013EE MOV EBP,ESP
//分配变量空间,我们可以计算一下
//defineVar()中有5个变量
//其中3个四字节变量,float在内存中
//占8个字节,double占8个字节
//那么一共28字节,换算成16进制是1C
//那么为什么会分配32(20H)字节呢?
//因为main函数在调用这个函数的时候
//push了一个4字节的地址在栈中(函
//数的返回地址)
//这里为了内存对齐(提高效率),所以
//编译器多分配了4个字节
004013F0 SUB ESP,20
//int a = 4;
//可以看到内存中只有地址,没有变量
//名,变量名只是给程序员看的
004013F3 MOV DWORD PTR SS:[EBP-4],4
//int b = 5;
004013FA MOV DWORD PTR SS:[EBP-8],5
//int c = 6;
00401401 MOV DWORD PTR SS:[EBP-C],6
//这里是小数赋值。没有用到浮点数
//指令跟SSE指令(扩展指令集浮点数
//运算)
//浮点数编码形式 参见 IEEE编码
//float abc = 4.5f
00401408 MOV EAX,DWORD PTR DS:[403064]
0040140D MOV DWORD PTR SS:[EBP-10],EAX
//double 精度比float高。
//采用两个32位寄存器存储
00401410 MOV EAX,CCCCCCCD
00401415 MOV EDX,4021CCCC
//double lop = 8.9
0040141A MOV DWORD PTR SS:[EBP-18],EAX
0040141D MOV DWORD PTR SS:[EBP-14],EDX
//恢复调用函数时 EBP的状态
00401420 LEAVE
//销毁变量 return
00401421 RETN
变量的定义用一张图总结就是:
在看varPtr()这个函数的反汇编
//后三步都跟前面一样
//至于为什么不按顺序存储数据,目前
//我不知道
004013CC PUSH EBP
004013CD MOV EBP,ESP
004013CF SUB ESP,10
//int a = 0x0F;
004013D2 MOV DWORD PTR SS:[EBP-10],0F
//用lea指令取到a的地址放在ptr中(也就是EBP-4),注意是地址(4字节)
004013D9 LEA EAX,DWORD PTR SS:[EBP-10]
004013DC MOV DWORD PTR SS:[EBP-4],EAX
//char * pt = (char*)ptr;
//这一步可以看出,其实没有真正的强
//制转换(还是4字节的地址),那么这一步的"强转"只是改变了计算机对这一
//段数据的处理方式
004013DF MOV EAX,DWORD PTR SS:[EBP-4]
004013E2 MOV DWORD PTR SS:[EBP-8],EAX
//int & sbs = a;
//可以看到引用其实跟指针是一样的。
//只不过引用一开始就绑定了一个地址
//往后就不能改变了(除了栈解退)
004013E5 LEA EAX,DWORD PTR SS:[EBP-10]
004013E8 MOV DWORD PTR SS:[EBP-C],EAX
//这个是伪指令,就是等同于},销毁
//栈中变量,恢复EBP,注意第一步有
//push EBP,对应起来
004013EB LEAVE
//return。函数返回调用者 恢复
//堆栈平衡
004013EC RETN
在看第三部分,constdefine()函数。首先我们并没有看到有关define的语句。
其实define语句是预处理,在编译之前预处理器对文件进行了预先处理。会生成一个XXX.i的文件
在这个文件中,预处理器已经把用到define的地方都进行了文本替换,由于条件关系,这里不列出了。
在看这个const对变量的影响。好像也没什么影响。
可以这么说,define才是真正的常量(数据在程序加载的时候,被加载到数据区),const只是编译器对在源代码的检查。实际上是伪常量(是辅助编写者,防止对一些不该改变的变量进行改变)
004013B0 PUSH EBP
004013B1 MOV EBP,ESP
004013B3 SUB ESP,10
004013B6 MOV DWORD PTR SS:[EBP-C],5
004013BD LEA EAX,DWORD PTR SS:[EBP-C]
004013C0 MOV DWORD PTR SS:[EBP-4],EAX
004013C3 MOV DWORD PTR SS:[EBP-8],3
004013CA LEAVE
004013CB RETN