ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C/C++内存管理底层地图:从进程地址空间到Valgrind排查

C/C++内存管理底层地图:从进程地址空间到Valgrind排查 我带的几个新人里十个有八个第一次写malloc的时候忘了free剩下两个反过来在多线程里对着同一块地址free了两次。C/C 内存管理这件事真正的难点从来不是记住申请和释放要配对而是脑子里得有一张进程地址空间的完整地图哪块内存归谁管、什么时候申请、什么时候归还、归还之后那块地址还能不能碰。这张图不清楚代码写再多也是在赌运气赌输了就是 SIGSEGV 或者更阴间的运行几小时才现形的内存踩踏。这篇东西想聊的就是这张图。从栈和堆的真实布局讲起把malloc背后的分配器逻辑、C 的 RAII 与智能指针、结构体对齐和缓存友好、内存池手写再到 VS Code 配置调试环境和 Valgrind/ASan 排查工具一条线串下来。它适合刚学完指针语法、程序却总莫名其妙崩的同学也适合写了几年 C 但内存问题一直靠经验拍脑袋的同行。我不打算复述手册只讲那些真正踩过坑才记得住的东西包括几个参数的推导过程和几段可以直接抄走的代码。1. C/C 内存管理的底层地图先搞清楚内存到底分几块1.1 进程地址空间的五个区域很多人学内存管理是从malloc和free开始的这个起点其实偏了。正确的起点应该是把进程地址空间摊开看一遍。在 Linux 上有个特别好用的命令写个空程序编译成a.out运行然后在另一个终端敲cat /proc/$(pidof a.out)/maps你就能看到自己这个进程的内存全貌。地址从低到高大致分成这么几块最下面是代码段.text装的是你编译出来的机器指令只读往上.data段和.bss段分别装已初始化的全局/静态变量和未初始化的全局/静态变量.bss段在可执行文件里其实不占空间加载时才由内核清零映射再往上是堆从低地址向高地址生长malloc拿的内存就在这儿然后是一大片共享库映射区libc.so这种动态库加载进来占了这一块最上面靠近高地址的是栈从高地址向低地址生长。这个从低到高、堆往上涨、栈往下压的布局不是随便排的它是一种折中堆和栈相向生长中间留出大块空洞谁先撞上谁就触发了地址空间不足。理解这个布局有个立竿见影的好处——当你的程序报段错误时你可以根据崩溃地址大致判断是动了代码段写只读内存、踩了未映射的堆外区域还是栈溢出了。我自己排查线上 core dump 的习惯动作就是先把崩溃时的地址和/proc/pid/maps对一下这一步经常能省掉半小时瞎猜。1.2 栈和堆的本质差异在哪这两个词天天说但很多人对它们的差异只有一个是自动的一个是手动的这种印象。我列一张表把真正影响你写代码的维度摆出来维度栈堆分配/释放方式编译器自动调整rsp手动调用malloc/free或new/delete生长方向高地址向低地址低地址向高地址典型大小8 MBLinux 默认ulimit -s可查受虚拟内存和物理内存限制几十 GB 量级分配速度移动一个寄存器纳秒级走分配器逻辑几十到几百纳秒碎片问题基本没有随栈帧弹出自动回收严重会产生外部碎片线程关系每个线程独立栈整个进程共享一个堆生命周期函数返回即失效直到free为止出错表现栈溢出、悬垂指针泄漏、越界、重复释放看这张表最该记住的是两件事一是栈内存函数一返回就没了任何把局部变量地址往外传的行为都是埋雷二是每个线程有自己独立的栈默认 8 MB但堆是共享的所以堆上操作在多线程下必须加锁或者用无锁结构而栈变量天
返回列表