9、C 语言内存管理知识点总结

C 进程内存布局、各内存区域(栈、数据段、代码段、堆)的存储内容与特性、静态数据及堆内存管理等方面

C 语言内存管理核心知识点总结

一、C 进程内存布局

C 语言程序运行时,其虚拟内存(从物理内存映射而来)具有固定的布局,不同区域存储不同类型的数据,且各区域特性各异。理解内存布局是掌握内存管理的基础。

1. 虚拟内存整体结构

  • 内核区(Kernel):位于虚拟内存高端(如 0xC0000000 以上),存储操作系统核心代码,应用程序不可访问。
  • 用户区 :应用程序可访问的区域,从低地址到高地址依次为:
    • 代码段:存放程序代码(用户代码、系统初始化代码)。
    • 数据段:存放静态数据(初始化 / 未初始化的全局变量、常量等)。
    • 堆(Heap):动态内存区域,由开发者手动分配和释放。
    • 栈(Stack):自动管理的内存区域,存放局部变量、函数参数等。
  • 不可访问区:低地址端(如 0x0~0x08048000),防止空指针访问有效内存。

2. 内存区域查询命令

  • ls -l 文件名:查看文件详细信息(权限、大小、修改时间等)。
  • file 文件名:查看文件格式(如 ELF 64-bit)、架构等。
  • size 文件名:查看文件在内存中的段分布(text、data、bss 的大小)。

二、栈内存(Stack)

栈是系统自动管理的内存区域,遵循 "先进后出" 原则,主要用于临时数据存储。

1. 存储内容

  • 环境变量:程序运行所需的环境配置(如编译器路径、库文件位置)。
  • 命令行参数 :通过main(int argc, char *argv[])传递的参数(argc为参数个数,argv为参数列表)。
  • 局部变量:函数内部定义的变量(包括函数形参),其作用域局限于函数内部。

2. 核心特性

  • 自动分配与释放:函数调用时,栈向下增长分配内存;函数退出时,栈向上缩减释放内存,无需开发者干预。
  • 空间有限 :默认大小通常为 2~10MB(如 Ubuntu 默认 8MB),可通过ulimit -s查询或修改。若申请超大局部变量(如char buf[1024*1024*10]),会导致栈溢出(Segmentation fault)。
  • 增长方向:从高地址向低地址增长。

3. 示例解析

|--------------------------------------------------------------------------------------------------------------------------------|
| void func(int a) { // 形参a存储在栈区 int b = 10; // 局部变量b存储在栈区 } int main() { func(5); // 调用func时,栈分配a和b的内存;函数退出后,内存自动释放 return 0; } |

三、数据段与代码段

数据段和代码段属于静态内存区域,程序加载时分配,生命周期贯穿整个程序运行过程。

1. 数据段(存放静态数据)

数据段细分为 3 个部分,均在程序加载时分配内存:

|---------|-----------|------------|-------------------------------------------------|
| 段名称 | 存储内容 | 初始化特性 | 示例 |
| .bss | 未初始化的静态数据 | 系统自动初始化为 0 | int num;(全局变量)、static int a;(静态局部变量) |
| .data | 已初始化的静态数据 | 保留初始化值 | int num = 100;(全局变量)、static int b = 20;(静态局部变量) |
| .rodata | 常量数据(只读) | 不可修改 | 整型常量(100)、字符常量('a')、字符串常量("hello") |

2. 代码段(存放程序代码)

  • .text 段 :用户编写的函数代码(如main函数、自定义函数)。
  • .init 段 :系统初始化代码,在main函数执行前运行(如堆、栈的初始化)。
  • 特性:只读(防止意外修改程序指令),大小固定。

3. 示例解析

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // .data段:已初始化全局变量 int g_data = 100; // .bss段:未初始化全局变量 int g_bss; int main() { // .rodata段:字符串常量 char *str = "hello"; // str在栈区,"hello"在.rodata段(只读) // .data段:已初始化静态局部变量 static int s_data = 200; // .bss段:未初始化静态局部变量 static int s_bss; return 0; } |

四、静态数据与 static 关键字

静态数据包括全局变量和静态局部变量,其生命周期与程序一致,作用域受static修饰影响。

1. 静态数据分类

  • 全局变量:定义在函数外部的变量,默认对整个工程可见。
  • 静态局部变量 :函数内部用static修饰的变量,作用域局限于函数,但生命周期贯穿程序。

2. static 关键字的作用

  • 修饰局部变量:将变量从栈区(临时变量)移至数据段(静态数据),函数退出后值保留(仅初始化一次)。

示例:

|---------------------------------------------------------------------------------------------------------------|
| int count() { static int c = 0; // 静态局部变量,仅初始化一次 c++; return c; } // 调用count()多次,返回值依次为1,2,3...(普通局部变量则始终返回1) |

  • 修饰全局变量 / 函数:限制其仅在本文件可见(避免工程内命名冲突)。

示例:

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // a.c文件 static int g_var = 100; // 仅a.c可见 static void func() {} // 仅a.c可见 // main.c文件 extern int g_var; // 报错:无法访问a.c的static变量 extern void func(); // 报错:无法访问a.c的static函数 |

3. 注意事项

  • 静态数据初始化语句仅执行一次(程序加载时)。
  • 未初始化的静态数据会被系统自动置为 0。
  • 过度使用静态数据会增加程序内存占用(直至程序退出才释放)。

五、堆内存(Heap)

堆是开发者可手动管理的动态内存区域,大小仅受物理内存限制,是内存管理的核心难点。

1. 核心特性

  • 动态分配与释放 :需通过 API 手动申请(malloc/calloc)和释放(free),系统不干预。
  • 增长方向:从低地址向高地址增长(与栈相反)。
  • 匿名性:无变量名,需通过指针访问。
  • 持久性:若不手动释放,会一直占用内存(直至程序退出),可能导致内存泄漏。

2. 堆内存操作 API

|--------|---------------|-----------------------|-------------------------------------|
| 函数 | 功能 | 特点 | 示例 |
| malloc | 申请指定大小的堆内存 | 未初始化(内容随机),需手动清零 | int *p = malloc(5 * sizeof(int)); |
| calloc | 申请指定数量和大小的堆内存 | 自动清零(初始化全 0) | int *p = calloc(5, sizeof(int)); |
| bzero | 清零指定内存区域 | 需配合malloc使用(calloc无需) | bzero(p, 5 * sizeof(int)); |
| free | 释放堆内存 | 仅能释放整块堆内存,释放后指针需置空 | free(p); p = NULL; |

3. 注意事项

  • 内存泄漏 :未调用free释放不再使用的堆内存,导致内存耗尽(尤其在循环或长期运行的程序中)。
  • 野指针 :free后指针未置空,继续访问会导致不可预期的错误(如修改已释放的内存)。
  • 重复释放 :同一堆内存被free多次,会触发段错误。
  • 越界访问 :访问超出申请大小的内存(如p[5]当申请了 5 个 int 时),可能破坏堆结构。

4. 示例解析

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #include <stdlib.h> #include <strings.h> int main() { // 用malloc申请堆内存(需手动清零) int *p1 = malloc(5 * sizeof(int)); bzero(p1, 5 * sizeof(int)); // 清零 for (int i = 0; i < 5; i++) { p1[i] = i; // 数组形式访问 } free(p1); // 释放内存 p1 = NULL; // 避免野指针 // 用calloc申请堆内存(自动清零) int *p2 = calloc(5, sizeof(int)); for (int i = 0; i < 5; i++) { *(p2 + i) = i; // 指针形式访问 } free(p2); p2 = NULL; return 0; } |

六、各内存区域对比

|------|-------|-----------|--------------|-------------|---------|
| 内存区域 | 管理方式 | 存储内容 | 生命周期 | 大小限制 | 增长方向 |
| 栈 | 系统自动 | 局部变量、函数参数 | 函数调用期间 | 较小(2~10MB) | 高地址→低地址 |
| 数据段 | 系统自动 | 静态数据、常量 | 程序运行期间 | 较大(受物理内存) | 固定 |
| 代码段 | 系统自动 | 程序代码 | 程序运行期间 | 固定 | 固定 |
| 堆 | 开发者手动 | 动态数据 | 手动释放前 / 程序退出 | 极大(受物理内存) | 低地址→高地址 |

总结

C 语言内存管理的核心是理解各区域的特性与边界:栈适合临时数据(自动管理),数据段和代码段适合静态数据(生命周期长),堆适合动态数据(灵活但需手动管理)。开发者需重点关注堆内存的申请 / 释放,避免内存泄漏、野指针等问题,同时合理使用static关键字控制静态数据的作用域。掌握内存布局与管理规则,是编写高效、健壮 C 程序的基础。

相关推荐
檀越剑指大厂10 分钟前
【Linux系列】服务器 IP 地址查询
linux·服务器·tcp/ip
十五年专注C++开发1 小时前
CMake进阶: externalproject_add用于在构建阶段下载、配置、构建和安装外部项目
linux·c++·windows·cmake·自动化构建
范纹杉想快点毕业1 小时前
《嵌入式 C 语言编码规范与工程实践个人笔记》参考华为C语言规范标准
服务器·c语言·stm32·单片机·华为·fpga开发·51单片机
Skylar_.1 小时前
嵌入式 - Linux软件编程:进程
java·linux·服务器
rannn_1112 小时前
【Linux学习|黑马笔记|Day4】IP地址、主机名、网络请求、下载、端口、进程管理、主机状态监控、环境变量、文件的上传和下载、压缩和解压
linux·笔记·后端·学习
白书宇2 小时前
5.从零开始写LINUX内核--从实模式到保护模式的过渡实现
linux·汇编·数据库·开源
野生柚子3 小时前
记录学习K8s 集群中OOM Killer的决策基准及执行流程
linux·运维
TLucas4 小时前
在CentOS 7上将PostgreSQL数据库从默认路径迁移到自定义目录
linux·运维·postgresql·centos
阿熊不凶4 小时前
c语言中堆和栈的区别
java·c语言·jvm