C语言内存布局

🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习

🎬擅长领域:驱动开发,嵌入式软件开发,BSP开发

❄️作者主页:一个平凡而乐于分享的小比特的个人主页

✨收录专栏:c语言重要知识点总结,本专栏旨在总结C语言学习过程中的易错点,通过调试代码,分析原理,对重要知识点有更清晰的理解

欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖

C语言内存布局

一、完全正确的内存布局划分

标准C程序内存布局(Linux ELF格式为例)

text 复制代码
高地址
┌──────────────────┐
│    内核空间       │
├──────────────────┤ ← 0x7fff... (栈顶)
│       栈         │ 包含:
│     (Stack)      │ - 局部变量
│                  │ - 函数参数
│                  │ - 返回地址
│                  │ - 保存的寄存器
├──────────────────┤ ← 栈向下增长
│                  │
│     内存映射区     │ 包含:
│   (Memory Map)   │ - 共享库
│                  │ - mmap映射的文件
├──────────────────┤ ← brk指针
│       堆         │ 包含:
│     (Heap)       │ - malloc/calloc分配的内存
│                  │   (向上增长)
├──────────────────┤
│       BSS        │ 包含:
│   (BSS段)        │ - 未初始化的全局/静态变量
│                  │ - 初始化为0的全局/静态变量
├──────────────────┤
│       DATA       │ 包含:
│   (数据段)        │ - 已初始化的全局/静态变量
├──────────────────┤
│      RODATA      │ 包含:
│   (只读数据段)    │ - 字符串常量
│                  │ - const修饰的全局/静态变量
├──────────────────┤
│       TEXT       │ 包含:
│   (代码段)        │ - 程序指令(机器码)
└──────────────────┘ ← 0x08048000 (32位Linux典型起始地址)
低地址

注意点:

  1. RODATA段是独立的:字符串常量和const全局变量放在独立的只读数据段
  2. DATA和BSS是分开的:已初始化和未初始化数据是分开的段
  3. const局部变量不在RODATA:只在栈上

二、各段的详细说明(修正版)

1. TEXT段(代码段)

c 复制代码
// 存放CPU执行的机器指令
// 属性:只读、可执行、共享

// 示例:所有函数代码都在这里
int add(int a, int b) {  // 函数指令在TEXT段
    return a + b;
}

// 查看方法:
$ objdump -d ./program  # 查看反汇编
$ readelf -S ./program | grep .text  # 查看text段信息

2. RODATA段(只读数据段)

c 复制代码
// 存放真正的只读数据
// 属性:只读、不可执行

// 正确的理解:
const int global_const = 100;      // ✅ 在.rodata段
const char* const msg = "Hello";   // ✅ 字符串在.rodata,指针在.rodata(因为const)
char* ptr = "World";               // ✅ 字符串在.rodata,但ptr变量本身在.data或.bss

// 字符串常量示例:
void func() {
    printf("%s\n", "Constant String");  // 字符串在.rodata段
    
    // 重要区别:
    char* p1 = "readonly";      // 指向.rodata
    char p2[] = "writable";     // 栈上数组,可以修改
    
    // p1[0] = 'R';  // ❌ 段错误(尝试修改只读内存)
    // p2[0] = 'W';  // ✅ 合法(修改栈上数据)
}

3. DATA段(已初始化数据段)

c 复制代码
// 存放已初始化的全局变量和静态变量
// 属性:可读写

int global_init = 42;              // ✅ 在.data段
static int static_init = 100;      // ✅ 在.data段

void func() {
    static int static_local = 200;  // ✅ 在.data段(不是栈上!)
}

4. BSS段(未初始化数据段)

c 复制代码
// 存放未初始化或初始化为0的全局/静态变量
// 属性:可读写

int global_uninit;                 // ✅ 在.bss段(实际清零)
int global_zero = 0;               // ✅ 在.bss段
static int static_uninit;          // ✅ 在.bss段

// BSS段的特点:不占用磁盘空间
$ size program
// 会显示text、data、bss的大小

5. 堆(Heap)

c 复制代码
#include <stdlib.h>

// 动态内存分配区域
int main() {
    // malloc从堆分配
    int* p = malloc(100 * sizeof(int));
    
    // 堆的特点:
    // 1. 手动管理(malloc/free)
    // 2. 向上增长(向高地址)
    // 3. 可能产生碎片
    
    // 验证堆地址:
    int* p1 = malloc(100);
    int* p2 = malloc(100);
    printf("p1=%p, p2=%p\n", p1, p2);  // p2 > p1(通常)
    
    free(p);
    free(p1);
    free(p2);
    return 0;
}

6. 栈(Stack)

c 复制代码
#include <stdio.h>

void stack_demo() {
    // 所有局部变量(非静态)都在栈上
    int a = 1;
    char buffer[100];
    float f;
    
    // const局部变量也在栈上!
    const int local_const = 100;  // 栈上,只读属性由编译器保证
    
    // 函数参数也在栈上
    void nested(int x) {
        // x在栈上
    }
    
    // 栈向下增长验证:
    int var1, var2, var3;
    printf("&var1=%p, &var2=%p, &var3=%p\n", &var1, &var2, &var3);
    // 输出:&var1 > &var2 > &var3(栈向低地址增长)
    
    nested(10);
}

三、const关键字的存储位置详解

const的不同情况:

c 复制代码
// 情况1:全局const变量(在.rodata段)
const int GLOBAL_CONST = 100;      // ✅ .rodata

// 情况2:静态const变量(在.rodata或.data,取决于是否初始化)
static const int STATIC_CONST = 200; // ✅ .rodata

// 情况3:局部const变量(在栈上)
void func() {
    const int LOCAL_CONST = 300;   // ✅ 栈上
}

// 情况4:const指针
const char* p1 = "hello";          // p1在.data/.bss,指向.rodata
char* const p2 = malloc(10);       // p2在栈或.data,指向堆
const char* const p3 = "world";    // p3在.rodata(如果全局)或栈

验证程序:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

const int c1 = 1;              // .rodata
static const int c2 = 2;       // .rodata
int g1 = 3;                    // .data
int g2;                        // .bss

int main() {
    static const int c3 = 4;   // .rodata
    const int c4 = 5;          // 栈上
    int l1 = 6;                // 栈上
    
    char* s1 = "literal";      // s1在栈,指向.rodata
    char s2[] = "array";       // 栈上数组
    
    printf("c1 (全局const):   %p\n", &c1);
    printf("c2 (静态全局const): %p\n", &c2);
    printf("c3 (静态局部const): %p\n", &c3);
    printf("c4 (局部const):   %p\n", &c4);
    printf("g1 (全局初始化):   %p\n", &g1);
    printf("g2 (全局未初始化):  %p\n", &g2);
    printf("l1 (局部变量):     %p\n", &l1);
    printf("s1 (字符串指针):   %p\n", s1);
    printf("s1自身地址:        %p\n", &s1);
    printf("s2 (字符数组):     %p\n", s2);
    
    // 查看各段地址范围(Linux)
    // cat /proc/self/maps 或使用pmap命令
    
    return 0;
}

四、通过工具验证内存布局

Linux下查看程序内存布局:

bash 复制代码
# 1. 编译时查看段信息
$ gcc -o program program.c
$ size program
   text    data     bss     dec     hex filename
   1234     567     890    2691     a83 program

# 2. 详细段信息
$ objdump -h program
$ readelf -S program

# 3. 运行时的内存映射
$ cat /proc/$$/maps  # 查看shell进程的内存映射
$ ./program &
$ cat /proc/$!/maps  # 查看程序的内存映射

# 4. 使用pmap
$ pmap -x <pid>

# 5. 查看特定符号的地址
$ nm program | grep -E "c1|c2|c3|g1|g2"

典型输出分析:

text 复制代码
00400000-00401000 r-xp 00000000 08:01 123456 /path/program  # text段
00600000-00601000 r--p 00000000 08:01 123456 /path/program  # rodata段
00601000-00602000 rw-p 00001000 08:01 123456 /path/program  # data段
7fff0000-7fff8000 rw-p 00000000 00:00 0                    # 堆/栈区域

五、重要概念澄清

1. const变量的本质:

  • 全局/静态const变量:放入.rodata段,真正只读
  • 局部const变量:栈上,编译器防止修改(非硬件保护)
  • const不等于常量:不能用于数组大小等需要编译时常量的地方

2. 字符串常量的存储:

c 复制代码
char* p1 = "Hello";    // "Hello"在.rodata段
char p2[] = "World";   // "World"在栈上(数组初始化时复制)

// 修改测试:
// p1[0] = 'h';  // ❌ 段错误(修改.rodata)
// p2[0] = 'w';  // ✅ 允许(修改栈)

3. 静态局部变量的特殊性:

c 复制代码
void counter() {
    static int count = 0;  // 在.bss段,只初始化一次
    count++;
}
// 虽然count作用域是函数内,但生命周期是整个程序运行期

六、完整的验证示例

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

// 全局变量
const int ro_int = 1;              // .rodata
int data_int = 2;                  // .data
int bss_int;                       // .bss
static int static_data_int = 3;    // .data
static int static_bss_int;         // .bss

// 函数声明
void check_memory_layout(void);

int main(int argc, char* argv[]) {
    printf("=== 内存布局验证 ===\n\n");
    
    // 打印各种变量的地址
    printf("1. 只读数据段 (.rodata):\n");
    printf("   ro_int:        %p\n", &ro_int);
    printf("   字符串常量:     %p\n", "constant string");
    
    printf("\n2. 数据段 (.data):\n");
    printf("   data_int:      %p\n", &data_int);
    printf("   static_data_int: %p\n", &static_data_int);
    
    printf("\n3. BSS段 (.bss):\n");
    printf("   bss_int:       %p\n", &bss_int);
    printf("   static_bss_int: %p\n", &static_bss_int);
    
    printf("\n4. 函数地址 (.text):\n");
    printf("   main:          %p\n", main);
    printf("   check_memory_layout: %p\n", check_memory_layout);
    
    printf("\n5. 栈变量 (局部变量):\n");
    int stack1 = 100;
    const int stack_const = 200;
    static int static_local = 300;  // 注意:这不是栈变量!
    
    printf("   stack1:        %p\n", &stack1);
    printf("   stack_const:   %p\n", &stack_const);
    printf("   static_local:  %p (实际在.data段)\n", &static_local);
    
    printf("\n6. 堆变量:\n");
    int* heap1 = malloc(sizeof(int));
    int* heap2 = malloc(sizeof(int));
    printf("   heap1:         %p\n", heap1);
    printf("   heap2:         %p\n", heap2);
    
    // 栈增长方向
    int a, b, c;
    printf("\n7. 栈增长方向验证:\n");
    printf("   &a=%p, &b=%p, &c=%p\n", &a, &b, &c);
    printf("   栈向%s地址增长\n", (&c < &a) ? "低" : "高");
    
    // 堆增长方向
    printf("\n8. 堆增长方向验证:\n");
    printf("   heap1=%p, heap2=%p\n", heap1, heap2);
    printf("   堆向%s地址增长\n", (heap2 > heap1) ? "高" : "低");
    
    free(heap1);
    free(heap2);
    
    // 检查完整的内存布局
    check_memory_layout();
    
    return 0;
}

void check_memory_layout(void) {
    printf("\n=== 地址排序验证 ===\n");
    
    // 期望的顺序(低地址到高地址):
    // 1. 代码段 (.text) - 函数地址
    // 2. 只读数据段 (.rodata) - const全局变量、字符串常量
    // 3. 数据段 (.data) - 已初始化全局/静态变量
    // 4. BSS段 (.bss) - 未初始化全局/静态变量
    // 5. 堆 (heap) - malloc分配的内存
    // 6. 栈 (stack) - 局部变量
    
    printf("\n检查地址顺序关系:\n");
    
    // 获取各种地址
    void* text_addr = (void*)main;
    void* rodata_addr = (void*)&ro_int;
    void* data_addr = (void*)&data_int;
    void* bss_addr = (void*)&bss_int;
    
    int stack_var;
    void* stack_addr = (void*)&stack_var;
    
    int* heap_var = malloc(100);
    void* heap_addr = (void*)heap_var;
    
    printf("text   (%p) < ", text_addr);
    printf("rodata (%p) < ", rodata_addr);
    printf("data   (%p) < ", data_addr);
    printf("bss    (%p) < ", bss_addr);
    printf("heap   (%p) < ", heap_addr);
    printf("stack  (%p)\n", stack_addr);
    
    free(heap_var);
}

七、总结要点

正确的内存布局理解:

  1. 代码段(TEXT):只读,可执行,存放程序指令
  2. 只读数据段(RODATA):只读,不可执行,存放字符串常量和const全局/静态变量
  3. 数据段(DATA):可读写,存放已初始化的全局/静态变量
  4. BSS段:可读写,存放未初始化或初始化为0的全局/静态变量
  5. 堆(HEAP):可读写,动态分配,向上增长
  6. 栈(STACK):可读写,自动管理,向下增长

关于const的关键事实:

  • const全局/静态变量 → 在.rodata段(真正只读)
  • const局部变量 → 在栈上(编译器防止修改,非硬件保护)
  • const不等于编译时常量 → 不能用const变量定义数组大小(C语言)

编程建议:

  • 了解各内存区域的特点,合理选择数据存储位置
  • 使用const表达意图,同时理解其限制
  • 通过工具验证内存布局,加深理解

参考博客:
C程序内存布局详解

相关推荐
Bigan(安)1 天前
【奶茶Beta专项】【LVGL9.4源码分析】09-core-global全局核心管理
linux·c语言·mcu·arm·unix
CoderYanger1 天前
C.滑动窗口-求子数组个数-越长越合法——2799. 统计完全子数组的数目
java·c语言·开发语言·数据结构·算法·leetcode·职场和发展
LinHenrY12271 天前
初识C语言(自定义结构:结构体)
c语言·开发语言
程序员Jared1 天前
深入浅出C语言——文件操作
c语言
CoderYanger1 天前
C.滑动窗口-求子数组个数-越长越合法——3325. 字符至少出现 K 次的子字符串 I
c语言·数据结构·算法·leetcode·职场和发展·哈希算法·散列表
点灯master1 天前
DAC8562的驱动设计开发
c语言·驱动开发·stm32
李绍熹1 天前
C语言基础语法示例
c语言·开发语言
法号:行颠1 天前
Chaos-nano协作式异步操作系统(六):`Chaos-nano` 在手持式 `VOC` 检测设备上的应用
c语言·单片机·嵌入式硬件·mcu·系统架构
南棱笑笑生1 天前
20251213给飞凌OK3588-C开发板适配Rockchip原厂的Buildroot【linux-6.1】系统时适配CTP触摸屏FT5X06
linux·c语言·开发语言·rockchip