C程序的内存到底怎么分的?一文讲透内存分区
1. 引言很多初学者在写 C 程序时常常会困惑变量到底存在哪里为什么局部变量和全局变量行为不一样为什么数组越界有时不报错、有时却段错误这些问题的答案都藏在 C 程序的内存分区里。本文用最直白的方式把 C 程序的内存布局讲清楚。2. 内存分区的整体图景一个 C 程序运行起来后操作系统会为它分配一块连续的虚拟地址空间。从高地址到低地址大致分为以下几个区域栈区Stack存放局部变量、函数参数、返回地址由编译器自动分配和释放。堆区Heap由程序员用malloc/free手动管理向高地址方向增长。全局区静态区存放全局变量和static修饰的变量程序启动时分配程序结束时释放。常量区只读区存放字符串字面量等只读数据修改会触发段错误。代码区Text存放编译后的机器指令只读。3. 栈区自动管理的临时仓库栈区是程序运行时最活跃的区域。每次调用一个函数系统就会在栈上压入一个「栈帧」用来存放这个函数的局部变量、参数和返回地址。函数返回时栈帧自动弹出内存随之释放。栈的特点是速度快、容量小、自动管理。默认栈大小通常在 1MB 到 8MB 之间取决于操作系统所以递归过深或定义过大的局部数组很容易导致栈溢出。#include stdio.h void func(int x) { int local x * 2; // local 存放在栈区 printf(%d\n, local); } int main() { func(10); // 调用函数时压栈返回时弹栈 return 0; }4. 堆区程序员自己管的大仓库堆区是程序员手动申请和释放的内存区域容量远大于栈受物理内存和虚拟内存限制。C 语言中通过malloc、calloc、realloc申请用free释放。堆的特点是容量大、速度慢、必须手动管理。忘记free会造成内存泄漏重复free或使用已释放的内存悬空指针则可能引发崩溃。#include stdio.h #include stdlib.h int main() { int *arr (int *)malloc(10 * sizeof(int)); // 在堆区申请 40 字节 if (arr NULL) { return 1; } arr[0] 100; printf(%d\n, arr[0]); free(arr); // 手动释放 return 0; }5. 全局区与静态区程序的一生全局变量和用static修饰的变量存放在全局区静态区。它们在程序启动时就被分配好直到程序结束才释放生命周期贯穿整个程序。这里要特别注意static修饰局部变量时变量的作用域仍然是函数内部但存储位置从栈区移到了全局区所以它的值在多次函数调用之间会保留。#include stdio.h int global_var 10; // 全局变量存放在全局区 void counter() { static int count 0; // 静态局部变量存放在全局区 count; printf(%d\n, count); } int main() { counter(); // 输出 1 counter(); // 输出 2 return 0; }6. 常量区与代码区只读的领地字符串字面量如hello存放在常量区这个区域是只读的。如果尝试修改它程序会直接崩溃段错误。代码区存放编译后的机器指令同样是只读的。#include stdio.h int main() { char *p hello; // hello 存放在常量区p 指向它 // p[0] H; // 错误常量区只读会段错误 printf(%s\n, p); return 0; }7. 一张表看懂内存分区区域存放内容分配方式生命周期容量栈区局部变量、函数参数编译器自动分配/释放函数调用期间小约 1-8MB堆区malloc 申请的内存程序员手动管理从申请到 free大受物理内存限制全局区全局变量、static 变量程序启动时分配整个程序运行期间中等常量区字符串字面量编译时确定整个程序运行期间只读代码区机器指令编译时确定整个程序运行期间只读8. 常见误区与实战建议理解了内存分区很多经典问题就迎刃而解函数返回局部数组的地址为什么危险因为局部数组在栈区函数返回后栈帧被释放地址变成悬空指针。为什么大数组要放在全局区或用 malloc因为栈区容量小大数组容易栈溢出。字符串常量为什么不能修改因为它在只读的常量区。内存泄漏怎么避免每次malloc都要配对free并养成检查返回值的习惯。建议初学者在写代码时时刻问自己一句这个变量存在哪个区它的生命周期有多长想清楚这两点C 语言的内存管理就成功了一大半。