内存空间布局:代码段、数据段,栈,堆 ,寄存器

内存空间布局详解:代码段、数据段、栈、堆与寄存器
本文详细介绍了内存的几个主要区域:代码段(存放执行代码)、数据段(存放已初始化全局变量)、BSS段(存放未初始化全局变量)、堆(动态内存分配)和栈(存放局部变量和调用信息)。还讨论了CPU寄存器的作用,以及进程如何通过段来访问内存。同时提到了堆栈的增长方向以及内存分配函数malloc()和free(),并提及了环境/参数节在存储系统环境变量和命令行参数中的角色。

首先看一段代码:

 #include <stdio.h>
  
  int glob_val;               //global uninitialized
  int glob_val2 = 2;          //global initialized
  //int glob_val5 = 4;
  //int glob_val6 = 4;
  static int glob_val3;       //static global uninitialized
  static int glob_val4 = 1;   //static global initialized
  
  int main(int argc , char *argv[]){
      int auto_val ;          //local uninitialized
      int auto_val2 = 3;      //local initialized
      static int stat_val ;   //static local uninitialized
      static int stat_val2 = 4;   //static local initialized
      char *p = "Hello World!";   //point to constant
  //  register reg_val = 5 ;      //register
  
      printf( "global uninitialized var's addr:"
              " %#08lx\n " , &glob_val );
      printf( "global initialized var's addr:"
              " %#08lx\n " , &glob_val2 );
      printf( "global initialized var's addr:"
              " %#08lx\n " , &glob_val3 );
      printf( "static global initialized var's addr:"
              " %#08lx\n " , &glob_val4 );
  
      printf( "constant value's addr:  %#08lx \n "
              "pointer's addr: %#08lx\n" , p , &p );
  //  printf( "register uninitialized var's addr:"
  //          " %#x\n " , &reg_val );
  
      printf( "local uninitialized var's addr:"
              " %#08lx\n " , &auto_val );
      printf( "local initialized var's addr:"
              " %#08lx\n " , &auto_val2 );
      printf( "static local uninitialized var's addr:"
              " %#08lx\n " , &stat_val );
      printf( "static local initialized var's addr:"
              " %#08lx\n " , &stat_val2 );
     return 0;
  }

输出的结果为:

global uninitialized var's addr: 0x601058
global initialized var's addr: 0x601040
global initialized var's addr: 0x601050
static global initialized var's addr: 0x601044
constant value's addr: 0x400698
pointer's addr: 0x7fff54c54688
local uninitialized var's addr: 0x7fff54c54680
local initialized var's addr: 0x7fff54c54684
static local uninitialized var's addr: 0x601054
static local initialized var's addr: 0x601048

然后我们再来看 APUE P164的配图 

从上面可以看出,内存大致分为几个段:

代码段(正文段):代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。

BSS:BSS段(bsssegment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。

堆(heap:堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

从上面程序运行的结果,可以看出:

1.函数代码存放在代码段。声明的类如果从未使用,则在编译时,会优化掉,其成员函数不占代码段空间。
全局变量或静态变量,放在数据段,注:按理来说 ,没有声明的全局变量或静态变量应该保存在BSS中,但结果看出感觉它们在一块

局部变量放在栈中,
用new产生的对象放在堆中,

常量保存在常量区,常量区地址比数据段地址更低

CPU寄存器:CPU寄存器,其实就是来控制代码段和数据段的指令及数据读取的地方,当然,CPU也有自己存放数据的地方,那就是通用寄存器里的数据寄存器,通常是EDX寄存器,C语言里有个register,就是把数据放在这个寄存器里,这样读取数据就相当的快了,因为不用去内存找,就省去了寻址和传送数据的时间开销。他还有一些寄存器是用来指示当前代码段的位置、数据段的位置、堆栈段的位置等等(注意这里存放的只是相应的代码或数据在内存中的地址,并不是实际的值,然后根据这个地址,通过地址总线和数据总线,去内存中获取相应的值)不然在执行代码的时候,指令和数据从哪取呢?呵呵。。。他里面还有标志寄存器,用来标识一些状态位,比如标识算术溢出呀等等。。。。。

寄存器是特殊形式的内存,嵌入到处理器内部。

每个进程需要访问内存中属于自身的区域,因此,可将内存划分成小的段,按需分发给进程。
寄存器用来存储和跟踪进程当前维护的段。偏移寄存器(Offset Registers)用来跟踪关键的数据放在段中的位置。在进程被载入内存中时,基本上被分裂成许多小的节(section)。我们比较关注的是6个主要的节:

(1) .text 节
    .text 节基本上相当于二进制可执行文件的.text部分,它包含了完成程序任务的机器指令。
该节标记为只读,如果发生写操作,会造成segmentation fault。在进程最初被加载到内存中开始,该节的大小就被固定。
(2).data 节
.data节用来存储初始化过的变量,如:int a =0 ; 该节的大小在运行时固定的。
(3).bss 节
栈下节(below stack section ,即.bss)用来存储为初始化的变量,如:int a; 该节的大小在运行时固定的。
(4) 堆节
堆节(heap section)用来存储动态分配的变量,位置从内存的低地址向高地址增长。内存的分配和释放通过malloc() 和 free() 函数控制。
(5) 栈节
栈节(stack section)用来跟踪函数调用(可能是递归的),在大多数系统上从内存的高地址向低地址增长。
同时,栈这种增长方式,导致了缓冲区溢出的可能性。
(6)环境/参数节

环境/参数节(environment/arguments section)用来存储系统环境变量的一份复制文件,
进程在运行时可能需要。例如,运行中的进程,可以通过环境变量来访问路径、shell 名称、主机名等信息。
该节是可写的,因此在格式串(format string)和缓冲区溢出(buffer overflow)攻击中都可以使用该节。
另外,命令行参数也保持在该区域中.

 

另外还有一篇文章:http://www.cppblog.com/prayer/archive/2009/08/17/93594.html我去验证BSS和数据段,没有验证成功。

 


<think>我们正在讨论数据段的区别。根据之前的引用和知识,我们可以从以下几个方面进行对比: 1. **分配方式**: - : 由编译器自动分配和释放,存放函数的参数值、局部变量等。 - : 由程序员手动分配和释放,如使用`malloc`、`calloc`等函数申请内存,使用`free`释放。 - 数据段: 包括初始化的全局变量和静态变量(.data段)以及未初始化的全局变量和静态变量(.bss段),由系统在程序启动时分配,程序结束时释放。 2. **管理方式**: - : 自动管理,效率高,但空间有限。 - : 手动管理,容易产生内存泄漏或碎片,但空间大。 - 数据段: 系统管理,整个程序运行期间都存在。 3. **大小限制**: - : 较小,通常几MB(取决于系统设置),过大会导致溢出。 - : 较大,受限于系统中有效的虚拟内存。 - 数据段: 大小在编译时确定,受限于可执行文件格式和系统限制。 4. **生命周期**: - : 函数执行期间存在,函数退出时自动释放。 - : 从分配开始直到手动释放或程序结束。 - 数据段: 整个程序运行期间都存在。 5. **地址增长方向**: - : 从高地址向低地址增长(向下增长)。 - : 从低地址向高地址增长(向上增长)。 - 数据段: 固定地址,不增长(但数据段本身在程序加载时确定,不会动态变化)。 6. **碎片问题**: - : 无碎片问题,因为分配和释放顺序严格(后进先出)。 - : 频繁的分配和释放会造成碎片。 - 数据段: 无碎片问题。 7. **访问速度**: - : 快,因为由硬件提供支持(指针寄存器)。 - : 相对慢,需要经过函数调用(如malloc/free)和可能的内存管理开销。 - 数据段: 访问速度和全局变量一样,通常较快。 8. **内容**: - : 函数参数、返回地址、局部变量等。 - : 动态分配的数据。 - 数据段: - 初始化的全局变量和静态变量(.data段) - 未初始化的全局变量和静态变量(.bss段,程序加载时由系统初始化为0) 我们可以用一个表格来总结: | 特性 | (Stack) | (Heap) | 数据段 (Data Segment) | |--------------|---------------------|---------------------|------------------------------| | **分配方式** | 自动 | 手动 | 系统在程序启动时分配 | | **释放方式** | 自动(函数返回时) | 手动(调用free) | 程序结束时系统释放 | | **大小限制** | 较小(默认几MB) | 较大(受系统内存限制) | 由程序定义的大小 | | **生命周期** | 函数执行期间 | 直到调用free | 整个程序运行期 | | **地址增长方向** | 向下(高地址->低地址) | 向上(低地址->高地址) | 固定地址(不增长) | | **碎片问题** | 无 | 有 | 无 | | **访问速度** | 快 | 较慢 | 快 | | **存储内容** | 局部变量、函数参数等 | 动态分配的数据 | 全局变量、静态变量 | 为了更直观,我们看一个简单的C代码示例: ```c #include <stdio.h> #include <stdlib.h> int global_var = 10; // 数据段(已初始化) int uninit_global_var; // 数据段(未初始化,.bss段) int main() { int local_var = 5; // static int static_var = 8; // 数据段(已初始化) int *heap_var = (int*)malloc(sizeof(int)); // 分配 *heap_var = 15; printf("地址: %p\n", &local_var); printf("地址: %p\n", heap_var); printf("全局初始化变量地址: %p\n", &global_var); printf("静态变量地址: %p\n", &static_var); printf("未初始化全局变量地址: %p\n", &uninit_global_var); free(heap_var); return 0; } ``` 运行结果可能如下(地址仅为示例): ``` 地址: 0x7ffd42aabb90 // 高地址区域 地址: 0x55a9e4e5e260 // 低地址区域(相对于) 全局初始化变量地址: 0x55a9e3ff4040 // 低地址 静态变量地址: 0x55a9e3ff4044 // 靠近全局变量 未初始化全局变量地址: 0x55a9e3ff6048 // 可能靠近.bss段 ``` 注意:地址通常很高,地址和数据段地址较低,但具体地址范围取决于系统。 总结关键区别: - ****:自动管理、高效、空间小、生命周期短(函数内)、向下增长。 - ****:手动管理、空间大、生命周期灵活(直到释放)、向上增长、有碎片问题。 - **数据段**:程序生命周期、存放全局和静态变量、无碎片、大小固定。 这些区别对于理解程序内存布局和避免内存相关错误至关重要[^1][^2][^4]。 §§相关问题§§ 1. 为什么的分配和释放速度比快? 2. 如何避免内存碎片问题? 3. 数据段中的.bss段和.data段有什么区别? </think>### 数据段的区别 在C语言中,**数据段**是三个关键的内存区域,它们在分配方式、生命周期和管理机制上有本质区别。以下是详细对比: | **特性** | ** (Stack)** | ** (Heap)** | **数据段 (Data Segment)** | |------------------|-------------------------------|-------------------------------|--------------------------------| | **分配方式** | 编译器自动分配/释放 | 程序员手动分配 (`malloc`/`free`) | 系统在程序启动时分配 | | **生命周期** | 函数执行期间 | 直到显式调用 `free()` | 整个程序运行期间 | | **存储内容** | 局部变量、函数参数、返回地址 | 动态分配的数据 | 全局变量、静态变量、常量 | | **增长方向** | 向下增长(高地址 → 低地址) | 向上增长(低地址 → 高地址) | 固定地址(不增长) | | **空间限制** | 较小(默认几 MB) | 较大(受系统内存限制) | 编译时确定 | | **管理开销** | 无(自动管理) | 需手动管理,易泄漏/碎片化 | 无(系统管理) | | **访问速度** | 快(硬件直接支持) | 较慢(需系统调用) | 快(直接寻址) | | **碎片问题** | 无 | 频繁分配释放易产生碎片 | 无 | --- ### 一、区(Stack) - **核心特性**: 编译器自动管理,存储函数调用时的临时数据。 ```c void example() { int a = 10; // 局部变量 → char buffer[100]; // 局部数组 → } // 函数结束时自动释放 ``` - **风险**: 大数组或深递归易导致**溢出**(如 `int arr[1000000];`)[^4]。 --- ### 二、区(Heap) - **核心特性**: 需手动管理,通过 `malloc`/`calloc` 分配,`free` 释放。 ```c int *arr = malloc(100 * sizeof(int)); // 分配 free(arr); // 必须手动释放 ``` - **注意事项**: - 忘记释放 → **内存泄漏** - 重复释放 → **程序崩溃** - 空间大但访问慢[^1][^2]。 --- ### 三、数据段(Data Segment) - **组成**: - **.data 段**:已初始化的全局/静态变量 ```c int global_var = 42; // .data 段 static int static_var = 100; // .data 段 ``` - **.bss 段**:未初始化的全局/静态变量(自动置0) ```c int uninit_global; // .bss 段 ``` - **特点**: 程序启动时分配,生命周期持续到程序结束[^1][^4]。 --- ### 四、地址分布示例 运行以下代码可观察地址差异: ```c #include <stdio.h> #include <stdlib.h> int global_data; // 数据段 (.bss) int main() { int stack_var; // int *heap_var = malloc(1); // static int static_data = 5; // 数据段 (.data) printf("地址: %p\n", (void*)&stack_var); printf("地址: %p\n", (void*)heap_var); printf("全局未初始化变量地址: %p\n", (void*)&global_data); printf("静态变量地址: %p\n", (void*)&static_data); free(heap_var); } ``` 典型输出: ``` 地址: 0x7ffd42aabb90 // 高地址区 地址: 0x55a1d4e8e260 // 中低地址区 全局变量地址: 0x55a1d3b04040 // 低地址区 静态变量地址: 0x55a1d3b04044 // 紧邻全局变量 ``` --- ### 五、关键区别总结 1. **管理方式** - :自动管理(安全但空间小) - :手动管理(灵活但高风险) - 数据段:系统管理(零开销)[^1][^4] 2. **使用场景** - :小型临时数据(如函数参数) - :大型动态数据(如文件缓冲区) - 数据段:持久化数据(如配置参数) 3. **常见错误** - 溢出 → 改写 `stack_arr[100000]` - 泄漏 → 忘记 `free()` - 数据段误用 → 滥用全局变量[^2]
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值