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

本文通过实验探讨了C++中const常量是否占用内存空间的问题,特别是在VS2008环境下局部变量及const常量在栈中的分配情况,并通过汇编代码详细解析了内存分配的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于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占不占用空间,我好像纠结了太久,花了好久时间,实际上我也知道只要会用就行不用太较真,可是哎,就是个爱较真的人。我是个菜鸟,如果有分析不对的,如果有不同见解的欢迎给我留言啊!

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

<think>嗯,用户现在想了解C语言中常量内存分配机制。首先,我需要回忆一下C语言中常量的类型和存储方式。根据引用里的信息,C语言的内存分配包括静态内存、动态内存、堆栈等。常量可能属于同的内存区域,具体要看它们的定义方式。 用户提到的常量可能包括字面常量const修饰的常变量、#define定义的宏常量,还有枚举常量。这些同类型的常量在内存中的存放位置可能同。比如,字面常量如字符串可能存储在只读数据段,而const变量可能放在栈或全局区,取决于定义的位置。 引用5提到,const修饰的常变量本质还是变量,但具有只读属性。所以它们的存储位置应该和普通变量类似,比如全局的const可能在全局区,函数内的const在栈上。而#define定义的宏在预处理阶段替换,占用内存,但字面常量如数字或字符串会有存储位置。 枚举常量在引用5中也有说明,它们属于整型常量,可能存储在代码段或只读数据段。需要确认枚举常量的具体存储方式,可能和编译器的实现有关。 另外,字符串常量通常存储在.rodata段或.data段的只读部分,这部分可能在静态存储区。而像全局变量和静态变量,根据引用1和3,全局变量在静态内存区,所以全局的常量可能也在这里。 动态分配的内存如malloc是在堆上,但常量一般会在这里,除非用户显式分配并初始化。过用户的问题是关于常量的,所以可能需要涉及堆。 总结下来,需要分类型讨论:字面常量const变量、宏、枚举,各自的内存分配位置。同时要引用相关的引用资料,比如引用5提到const是常变量,引用1和3关于静态内存和堆栈的区别。 可能用户还混淆了常量和常变量,需要明确说明const变量和真正常量的区别。比如,const变量实际可能存储在栈或全局区,而字面量在只读段。 另外,需要注意内存段的划分,如代码段、数据段、BSS、堆、栈等。常量的存储位置可能在这些同的段中,具体取决于类型和定义方式。 需要确保回答结构清晰,分点说明每种常量的内存机制,并引用正确的资料。同时生成相关问题时要围绕C语言常量内存分配的其他方面,比如和动态内存的区别,或者常量类型的使用场景。</think>### C语言常量内存分配机制 在C语言中,常量内存分配方式取决于其类型和定义方式,主要分为以下几类: --- #### 1. **字面常量** 字面常量(如整数、浮点数、字符串字面量)存储在**只读数据段(`.rodata`)**中。例如: ```c printf("Hello"); // 字符串字面量"Hello"存储在.rodata段 int a = 10; // 字面量10可能直接嵌入指令或存储在.rodata段 ``` - **特点**:生命周期与程序一致,可修改,内存由编译器静态分配[^3][^5]。 --- #### 2. **`const`修饰的常变量** `const`修饰的变量本质是**只读变量**,其存储位置取决于定义位置: - **全局作用域**:存储在**全局/静态存储区**(`.data`或`.bss`段)。 ```c const int MAX = 100; // 存储在.data段(已初始化)或.bss段(未初始化) ``` - **局部作用域**:存储在**栈区**,函数执行时分配,结束后释放。 ```c void func() { const int local_const = 5; // 存储在栈区 } ``` - **特点**:语法层面可修改,但通过指针仍可能间接修改(依赖编译器实现)。 --- #### 3. **`#define`定义的宏常量** 宏常量通过预处理器替换,**占用内存**。例如: ```c #define PI 3.14159 // 编译时替换为字面量,无独立内存空间 ``` - **特点**:仅文本替换,类型安全性较低,无内存分配过程[^5]。 --- #### 4. **枚举常量** 枚举常量本质是**整型常量**,通常存储在**代码段或`.rodata`段**: ```c enum Color { RED, GREEN, BLUE }; // RED=0, GREEN=1, BLUE=2 ``` - **特点**:编译时转换为整数值,内存分配方式与字面常量类似[^5]。 --- ### 内存区域对比 | 常量类型 | 存储区域 | 生命周期 | 是否可修改 | |-----------------|------------------|----------------|-----------| | 字面常量 | `.rodata` | 程序运行期 | 否 | | 全局`const`变量 | 全局/静态存储区 | 程序运行期 | 否[^1] | | 局部`const`变量 | 栈区 | 函数执行期 | 否(语法层面)| | 枚举常量 | 代码段/`.rodata` | 程序运行期 | 否 | --- ### 关键机制 1. **只读保护**:字面常量和全局`const`变量通常位于只读段,修改会触发段错误[^3]。 2. **编译器优化**:常量可能直接嵌入指令(如立即数),减少内存访问。 3. **符号表管理**:编译器将常量名称映射到固定地址,简化运行时寻址。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值