6. 代码区:只读、函数代码存储

说到代码区,我脑子里第一个蹦出来的印象就是「只读」两个字。你想想看,程序跑起来之后,函数代码要是能被随便改,那不乱套了?

代码区,也叫文本段(Text Segment),是进程虚拟地址空间中一块非常特殊的区域。它存放的就是你写的那些函数指令——编译后的机器码。这块区域在程序加载时就被固定下来,运行期间只读,不可写。

为什么必须是只读的?

这个问题我当年刚学的时候也困惑过。代码放在内存里,为什么不让改?

其实原因很直接:安全。如果代码区可写,恶意程序就能注入代码,改写你的函数逻辑。比如你调用 check_password(),结果它被改成了永远返回 true——那还谈什么安全?

另外,共享也是一个重要原因。同一个程序启动多个进程,它们的代码区可以共享同一块物理内存。如果代码区可写,共享就没法做了——你改了我怎么办?

核心要点:代码区只读,是为了安全 + 共享。这是现代操作系统的基本设计原则。

代码区里到底放了什么?

说白了,就是你的函数。包括:

  • 你自己写的普通函数、成员函数
  • 静态函数(包括全局静态函数和类静态成员函数)
  • 编译器生成的隐含函数(比如构造函数、析构函数、虚函数表的一部分)
  • 一些只读的常量数据(比如字符串字面量,在某些平台上)

嗯,这里要注意:字符串字面量在有些编译器里放在代码区,有些放在只读数据区(.rodata)。但不管放哪,都是只读的。我曾经见过新手写 char *p = "hello"; p[0] = 'H';,然后程序崩溃——就是因为试图修改只读内存。

代码区的生命周期

代码区的生命周期和进程一样长。从程序加载开始,到进程退出结束。它不像堆和栈那样动态变化,也不存在「分配」和「释放」的概念。

我个人习惯把内存区域分成三类:

区域 读写属性 生命周期 典型内容
代码区 只读 进程生命周期 函数指令、部分常量
读写 函数调用期间 局部变量、函数参数
读写 手动管理 动态分配的对象

代码区与函数指针

函数指针指向的就是代码区的地址。你写 void (*fp)() = &func;,这个 fp 存的就是 func 在代码区中的入口地址。

我在项目中遇到过一个问题:有人试图通过函数指针修改函数行为,结果段错误。原因就是函数指针指向只读区域,写操作不被允许。

小技巧:如果你需要动态替换函数行为,别想着改代码区。用虚函数、函数对象、或者 std::function 来实现多态,这才是正道。

代码区的大小

代码区的大小在编译时就确定了。你写的代码越多,代码区就越大。但要注意:代码区大小不等于可执行文件大小。可执行文件里还包含了符号表、调试信息、资源数据等,这些不会全部加载到代码区。

我曾经优化过一个嵌入式项目,代码区快满了。那时候我一个个函数看,把没用的调试日志、死代码删掉,硬是腾出了 30% 的空间。嗯,代码区虽小,但寸土寸金。

代码区与多线程

多线程环境下,所有线程共享同一个代码区。这意味着:

  • 函数代码只有一份,所有线程都能执行
  • 不需要为每个线程复制代码
  • 代码区不需要加锁——因为是只读的

你想想看,如果每个线程都复制一份代码,内存开销得多大?共享代码区是操作系统做的一个非常聪明的设计。

避坑指南

我曾经踩过一个坑:在嵌入式系统里,把一个大数组定义成局部变量,结果栈溢出了。后来我把数组改成 static const,它就被放到了代码区(或者只读数据区),栈的压力瞬间没了。

所以,如果你有只读的大块数据,比如查找表、配置数据,不妨考虑用 static const 定义。这样它们会进入只读区域,既安全又节省栈空间。

警告:不要试图用 const_cast 去掉 const 属性后修改只读区域的数据。这是未定义行为,程序可能崩溃,也可能悄无声息地出错——比崩溃更可怕。

代码区的内存布局

为了让你更直观地理解,我画了一张图:

进程虚拟地址空间布局 高地址 栈区(Stack) 局部变量、函数参数、返回地址 堆区(Heap) 动态分配的内存(new/malloc) 数据区(Data Segment) 全局变量、静态变量(可读写) 代码区(Text Segment) 函数指令、只读常量(只读) 低地址

从这张图可以看得很清楚:代码区在最底部(低地址),向上依次是数据区、堆区、栈区。栈区在高地址,向下增长。堆区向上增长。这样设计是为了让堆和栈能灵活使用中间的地址空间。

总结

代码区是程序运行的基础。它只读、共享、生命周期与进程一致。理解代码区,能帮你更好地理解函数指针、多线程、内存安全这些概念。

我个人觉得,搞懂代码区是理解整个内存模型的起点。你想想看,如果连代码放哪、能不能改都不清楚,后面谈堆栈管理、智能指针,根基就不牢。

嗯,这一章就到这里。代码区虽然简单,但它是整个程序的「骨架」。骨架稳了,后面的血肉才好往上长。

一句话记住:代码区只读,函数代码安家。别想着改它,用多态才是正道。

专注资料整理