当前位置: 首页 > news >正文

C语言内存分配,栈区、堆区、全局区、常量区和代码区都是什么

代码装在内存里,却没人真见过它长啥样,程序跑着跑着就崩了,到底哪块儿出了问题?我今天自己动手扒了一遍。

我以前以为内存分区就是课本上画的那几条横线,栈在上头堆在下头,什么常量区数据区的,背得挺熟。结果写了个小程序,拿`/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;`进`.rodata`,`const 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不是存在某个“特殊区”,就是给每个线程悄悄划了一小块地,名字叫`dtv`,glibc自己记着。还有`mmap`映文件,一个`open+read`变`open+mmap`,读大文件时少一次拷贝,但内存用量直接变大——这些都不是C语言教的,是Linux给你开的后门。

最后我把所有变量地址全打出来:全局`g`在`0x404024`(`readelf`确认是`.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`里改了字符串?地址一打,权限一看,八成心里就有数了。

http://www.gsyq.cn/news/1437897.html

相关文章:

  • 量子算法解码二次Reed-Muller码的技术解析
  • 脉冲神经网络整数混合精度训练技术解析
  • 保姆级教程:在VSCode+PlatformIO上为ESP32驱动1.3寸TFT屏(ST7789芯片)
  • 2026全国logo设计优质机构推荐榜:农产品商标设计/医疗健康logo设计/医疗健康商标设计/原创商标设计/商标设计全包/选择指南 - 优质品牌商家
  • 近阈值电压下大规模MIMO的ABFT容错技术解析
  • Pico VR开发避坑指南:从射线穿模到UI点击无效,这些坑我都帮你填平了
  • 不锈钢加强筋瓦斯抽放管实测评测:环氧涂层螺旋焊管、瓦斯螺旋焊管、矿用涂层加强筋螺旋焊管、矿用瓦斯管、矿用螺旋焊管选择指南 - 优质品牌商家
  • 2026年AI网络推广服务排名,佐途科技口碑好且价格实惠 - mypinpai
  • 第3篇|LocationKit 定位服务踩坑实录与最佳实践
  • 别再死记公式了!用Excel快速搞定Buck/Boost电路的电感电容选型(附模板下载)
  • H2矩阵块Krylov求解器优化与工程实践
  • 椒图蜘蛛监控与维护系统 网站蜘蛛数据统计
  • 别再手动接线了!用LabVIEW Modbus库高效读写PLC寄存器(以三菱FX系列为例)
  • Prompt 完全指南:大模型时代的沟通艺术与工程科学
  • Slurm集群管理:除了sinfo,你还可以用这些方法查看节点负载和GPU使用情况
  • 别再只用TileMap了!用Godot4.2手搓一个轻量级可交互网格节点(附完整源码)
  • 不止于删除:深入理解UOS/Linux桌面应用关联与MIME类型配置(以统信1060为例)
  • 音频传输系统——第三周
  • AI时代生存指南:不做被淘汰的“机械人”,三种人生态度你属于哪一种?
  • 从热敏到针式:手把手教你为单片机项目选配合适的微型打印机模块
  • 【Redis】 核心知识点全面讲解
  • Cortex-A7 L2缓存电源管理机制与优化策略
  • 别再只会复制代码了!手把手教你从STM32F407手册出发,搞懂CubeMX定时器PWM配置(附TB6612驱动避坑)
  • 统信UOS 1070安装后必做的10件事:从软件商店到AI助手,快速上手新系统
  • 2026年6月新消息:防火检测服务商深度盘点与联系方式指南 - 2026年企业资讯
  • 你的BetaFlight电流为啥总不准?从采样电路到代码,一次讲清所有硬件‘坑’
  • 火锅底料批量采购技术全解析:适配多场景的选型与风控 - 优质品牌商家
  • Windows Server 2022组策略实战:从桌面管理到IE配置,一份给运维新手的保姆级清单
  • 2026现阶段河北镀锌网片定做厂家选择与价值深度剖析 - 2026年企业资讯
  • 2026年可靠的鸿鱼锌锡合金钻尾螺丝哪家好?深度解析行业优选 - 2026年企业资讯