代码装在内存里却没人真见过它长啥样程序跑着跑着就崩了到底哪块儿出了问题我今天自己动手扒了一遍。我以前以为内存分区就是课本上画的那几条横线栈在上头堆在下头什么常量区数据区的背得挺熟。结果写了个小程序拿/proc//maps一瞅全乱了——r-xp段好几个rw-p连着三块还有块r--p标着压根没听过。原来课本说的“五区”只是老师为了好讲硬凑出来的说法。C语言标准里根本没提“栈”“堆”这两个字。翻了翻PDF版C11草案在6.2.4节只写了“自动存储期”和“静态存储期”至于存在哪儿标准说不归我管。这话听着有点甩手不管的意思但其实很实在——编译器和操作系统才决定东西放哪儿。GCC把int x 5;扔.data把char s hi;塞.rodata而const char *p bye;呢字符串还是在.rodata指针p却在.data里值是指向那个只读地址。这下明白为啥改字符串字面量会段错误了——不是程序错了是硬件直接拦住不让你写。我试了size a.out发现.bss大小居然是0虽然代码里写了int a, b, c;三个没初始化的全局变量。再查readelf -S果然.bss标着ALLOC但没标LOAD意思就是磁盘上不存加载时系统顺手清零就行。省空间也快。.rodata却老老实实占着硬盘因为“hello”这种字符串得从文件读进去。所以const int x 123;进.rodataconst int y z 1;z是变量就不行——编译时算不出来只能放.data。栈也没想象中那么“高大上”。我写了个函数里头开个int arr;一跑就段错误。ulimit -s一看8192KB。不是编译器拦你是Linux内核在页表里设的红线越界就发SIGSEGV。奇怪的是用alloca(100000)也崩但崩得更“脆”连调试信息都少半截。栈确实是CPU和编译器联手搭的架子call推帧ret弹帧干净利落但没缓冲区。堆更现实。malloc(100)看起来简单背后是glibc的ptmalloc在忙活查tcache有没有现成块没有就找fastbin再不行就碰brk或mmap。我用malloc_stats()打出来看到“fastbins”“unsorted bin”一堆名词才懂为啥小内存分配其实挺快——很多情况根本没动系统调用。但malloc(200*1024)200KB就直接mmap了/proc//maps里多了个独立的rw-p段跟堆主线断开了。不是malloc偷懒是OS觉得这么大一块单独管更省事。我还试了thread_local int t 99;pstack看不出啥但/proc//maps里多了一小块带stack:xxx标记的内存每个线程一份。TLS不是存在某个“特殊区”就是给每个线程悄悄划了一小块地名字叫dtvglibc自己记着。还有mmap映文件一个openread变openmmap读大文件时少一次拷贝但内存用量直接变大——这些都不是C语言教的是Linux给你开的后门。最后我把所有变量地址全打出来全局g在0x404024readelf确认是.data字符串abc在0x402004.rodata局部int x在0x7fff...栈malloc回来的在0x7f...堆。地址数字本身没意义但看权限就懂栈地址rw-p代码地址r-xp.rodata地址r--p。W^X安全模型就靠这个撑着——写代码段不行。执行数据段也不行。现代系统不是靠程序员自觉是靠硬件页表死卡着。验证这事真不难。写个十几行的C文件gcc -g -O0编译后台跑起来查pid再猫进/proc//maps左边地址右边权限中间路径一目了然。pstack看栈帧cat /proc//status看总内存。不需要懂汇编也不用背术语眼睛盯着r-xp和rw-p比啥都准。很多人说堆比栈慢我测了下100次malloc(8)和int x时间差不到0.01毫秒。真慢的是free之后又malloc碎片多了就得合并。或者你malloc一兆再free结果别人malloc两百字节卡半天——不是堆慢是管理策略在权衡。选栈还是堆看生命周期就行函数里用走栈要传出去、要活久点走堆。别的都是障眼法。我删掉了所有“常量区”“静态区”的笔记。.rodata就是.rodata它跟.data同属数据段但权限不同。static变量放在.data或.bss跟“静态”俩字没关系只跟有无初始值有关。术语越模糊debug越抓瞎。现在我看程序不先想语法先想内存。printf(%s, p);崩了先查p指向哪——栈上局部数组已释放堆上free过头还是.rodata里改了字符串地址一打权限一看八成心里就有数了。