关于const常量的内存分配问题

关于C++中局部变量在栈中分配被无数人提到,本人也查看了别人的很多资料。我对这个问题的研究开始于高质量程序设计指南中关于const常量是否占内存空间的说法,原文为:在C语言中,用const定义的常量其实是值不能修改的变量,因此会给它分配存储空间;但是在C++中,const定义的常量要具体情况具体对待:对于基本数据类型的常量,编译器会把它放到符号表中而不分配存储空间,而ADT/UDT的const对象则需要分配存储空间(大对象)。还有一些情况下也需要分配存储空间,例如强制声明为extern的符号常量或取符号常量的地址等操作。读到这段话,我想要不我在VS2008下写个程序试验一下,于是这一试验,就引发了一系列问题。我在VS2008中编写的程序如下所示:

int main(int argc, char* argv[])
{
 char ch=0;
 int e=5;
 int f=6;
 const int a=7;
 int b=8;
 int c=9;
 printf("%d\n",&ch);
 printf("%0x\n",&e);
 printf("%0x\n",&f);
 printf("%0x\n",&b);
 printf("%0x\n",&c);
  cin>>ch;
 cin>>ch;
 return 0;

}

该程序实际上非常简单,就是定义了几个整数和一个常量,然后输出几个整数的地址,我得到的结果为:


 从图中可以看出e、f整型变量之间相距12个字节,也就是在VS2008中给每个整型变量会分配12个字节,其实数据占4个字节,其余8个字节填充为CCH,为什么这么做暂时还不清楚。但是f和b之间却相差了24个字节,所以我推断实际上编译器给常量a也分配了地址。实际上确实编译器给常量也分配了地址。

我是怎么知道的呢?当我看到如图运行结果时,我就想让我看看内存中的栈区吧,我就知道到底是怎么回事了,可是由于比较菜鸟,所以不知道怎么在VS2008下看内存,于是百度啊,找到了方法。具体为:

首先在程序中设置一个断点,为了方便调试,我把断点加到了最后。然后按F5或点启动调试按钮,这时程序就进入到了调试状态。第三步:调试—>窗口—>内存—>内存1就可以看到内存中的数据了。其实你会发现调试窗口中还有好多东西,比如反汇编啊,寄存器啊之类的。这些都可以在调试中用到。比如点击反汇编则会进入程序对应的汇编代码,那个汇编代码还是很有意思的,待会再细细分析一下。在反汇编代码中也可以设置断点进行单步调试来一条一条执行汇编指令。

注意:在VS2008没有进入调试状态时,调试—>窗口中是不会有那些子菜单出现的,所以得先设一个断点

现在我已经打开了内存窗口,可是密密麻麻从哪里看起呢,于是就想到了汇编代码,先看看这段程序汇编代码吧:


 汇编代码第一行:push ebp //将基址指针寄存器入栈,基址指针寄存器用来确定堆栈段中某一存储单元的地址,这是执行每个函数之前都要进行的保护现场的工作

第二行: mov ebp,esp //将堆栈指针移入基址指针

第三行:sub esp,108h//堆栈指针减去108h,实际上就是给当前程序分配108h个内存空间

前三行汇编代码实际上是分配内存的前期工作

接下来的三行是保护基址寄存器EBX、源变址寄存器SI和目的变址寄存器DI。在执行push操作时堆栈指针ESP进行是减法操作,每有一个寄存器入栈,ESP都会减去4。

在接下来的四行实现变量内存空间的初始化,初始化为CCH

lea   edi,[ebp-108h]//将要初始化的内存空间的首地址送到目的变址寄存器EDI中。LEA是有效地址送寄存器指令

mov  ecx ,42H//将42H送计数器CX中,为下一条REP指令做好准备

mov eax ,0CCCCCCCCH //将要初始化的数值送到EAX寄存器中

rep stos dword ptr es:[edi]//实现初始化操作  REP指令用于与其他指令配合起来实现重复传送功能,传送次数由ECX决定,每传送一次数据,ECX都将减1,等ECX减为0时传送结束。在这里给ECX赋值为42H,说明会传送42H次,而每次都会传送四个字节,所以会初始化42H乘以4个内存单元,也就是108H个内存单元。stos是存入串指令,该指令会把AX中的数据存入DI寄存器所指向的内存单元。stos指令经常用于初始化一个缓冲区。stos指令的指令格式为:stos 目标地址。在这条指令中目标地址为:es:[edi]。在这里dword ptr为属性操作符,用于表示进行的是双字传送,而不是字或者字节。

接下来就是具体变量的初始化: mov  byte ptr[ch],0  //将ch所对应的内存单元初始化为0,在这里ch代表变量ch的内存地址,方括号用来表示是直接寻址方式。可以看出ch初始化时只初始化了一个字节,但是实际上ch占据了四个字节。同样整型变量初始化时只初始化4个字节,但是分配了12个字节的空间。从汇编代码可以看出实际上常量a和其余变量一样都在栈中分配了存储空间。

我们通过图来分析一下这个程序占用的栈空间:


 

通过分析内存可以看出该程序共占用了108H字节单元的内存地址,字符ch的内存地址为0012FF63H,之前有四个字节没有用,我也不知道编译器为什么要这样安排,字符e的内存地址为0012FF54H。常量a的地址为0012FF3CH。为了const占不占用空间,我好像纠结了太久,花了好久时间,实际上我也知道只要会用就行不用太较真,可是哎,就是个爱较真的人。我是个菜鸟,如果有分析不对的,如果有不同见解的欢迎给我留言啊!

 分析到这不知该如何往下写了,先就这样吧,总之是学会了一点点汇编和变量分配以及调试。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: const常量和#define的区别在于: 1. const常量是在编译时分配内存,而#define是在预处理时进行文本替换,不会分配内存。 2. const常量有类型,可以进行类型检查,而#define没有类型,只是简单的文本替换。 3. const常量可以进行地址取值操作,而#define不可以。 4. const常量可以在多个文件中共享,而#define只能在定义它的文件中使用。 5. const常量可以进行调试,而#define不可以。 因此,建议在定义常量时优先使用const,而不是#define。 ### 回答2: const常量是C++中常用的一种数据类型。定义了const常量之后,它的值不能改变,因此const常量也被称为“只读变量”。 定义const常量的语法格式如下: const 数据类型 常量名 = 常量值; 其中,const表示定义一个常量,数据类型指定了常量的类型,常量名指定了常量的名字,常量值则指定了常量的值。 与const常量类似的是#define宏定义,它们的区别在于前者是编译器处理的常量,后者是预处理器处理的常量。宏定义不会对常量进行类型检查,容易出现错误,在C++中往往使用const常量来代替宏定义。 const常量非常有用,它可以保证程序在运行过程中某些数值不被无意中修改。另外,const常量也可以作为参数传递给函数,这样可以避免函数中修改传入参数的值,增强程序的可读性和健壮性。在类中,const常量也可以用来声明成员变量,表示它的值在对象生命周期内不会改变。 总之,const常量是C++中非常重要的一种数据类型,它可以保证程序的安全性和稳定性,非常适合用于需要保证某些值不被随意修改的场合。 ### 回答3: let变量在JavaScript中的区别是什么? 在JavaScript中,const常量和let变量是两种常用的声明变量的方式。它们与var的不同之处在于它们在声明后具有不同的作用域和可变性。具体区别如下: 1. const常量:声明一个常量时,必须立即初始化常量的值,不能再次赋值。而且,const声明的常量是块级作用域,在声明的块级别内可见。此外,如果尝试重新分配const常量,将会引发TypeError错误。 2. let变量:初始化后,let声明的变量可以更改其值。与const一样,let变量也是在块级别的作用域中声明的,但是它在作用域中声明的变量可以被更改赋值。 总的来说,const常量和let变量在声明后都可以使用块级作用域,这意味着它们在声明块级别内可见,而不是在函数级别内。但是,const常量以初始化值之后不能进行赋值,同时,let变量可以被重新赋值。选择const还是let变量,需要根据实际需求和定义变量的用途来判断。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值