🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习
🎬擅长领域:驱动开发,嵌入式软件开发,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典型起始地址)
低地址
注意点:
- RODATA段是独立的:字符串常量和const全局变量放在独立的只读数据段
- DATA和BSS是分开的:已初始化和未初始化数据是分开的段
- 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);
}
七、总结要点
正确的内存布局理解:
- 代码段(TEXT):只读,可执行,存放程序指令
- 只读数据段(RODATA):只读,不可执行,存放字符串常量和const全局/静态变量
- 数据段(DATA):可读写,存放已初始化的全局/静态变量
- BSS段:可读写,存放未初始化或初始化为0的全局/静态变量
- 堆(HEAP):可读写,动态分配,向上增长
- 栈(STACK):可读写,自动管理,向下增长
关于const的关键事实:
- const全局/静态变量 → 在.rodata段(真正只读)
- const局部变量 → 在栈上(编译器防止修改,非硬件保护)
- const不等于编译时常量 → 不能用const变量定义数组大小(C语言)
编程建议:
- 了解各内存区域的特点,合理选择数据存储位置
- 使用const表达意图,同时理解其限制
- 通过工具验证内存布局,加深理解
参考博客:
C程序内存布局详解