目录
全局const int b什么时候在.rodata,什么时候在.data
前言
虽然是我只是记录学习,但是这个CSDN文章评分真是太怪了。上一篇没采用问答式写法,掺了点自己看法,结果评分还没有问答高()。这篇就还是以问答式记录吧。
.rodata 只读段(Read‑Only Data)
前置回忆:
.text段存放程序机器指令;.data存已经初始化的全局变量;.bss存放未初始化全局变量。
.rodata 是只读数据段,属于可执行程序 exe 磁盘文件中的段,程序加载时映射到进程虚拟地址空间。专门存放不会被修改的常量数据 **。**
最典型例子:字符串字面量。
c
char *p = "hello world";
"hello world"这个字符串常量,编译的时候就被放进.rodata段。 它不在栈,不在堆,不在.data,放在.rodata。
⚠️关键:这块内存硬件层面标记为只读,CPU 不允许写操作。试图修改它,程序直接崩溃(段错误)。

📝检验问题(3 题,请你回答)
.rodata里面存放什么类型的数据?char *p="hello";,字符串"hello"存放在哪个段?- 尝试修改
p[0]='x'为什么程序会崩溃?
你回复这三道题答案,有额外疑问也可以一起提出来。答完我继续下一小段讲解。
:
1.不会被修改的常量数据
2.rodata段
3.因为p[0]属于可rodata段 只能读取
:
✅全部答对。补充一点细节:是操作系统页表把.rodata 对应的物理页设置成只读权限,硬件 MMU 检测到写操作,触发保护异常,程序报段错误直接终止。
与const修饰的变量
并不是所有带const修饰的变量,都会进入 .rodata。 这是考研最高频的坑点。
c
// ①全局const变量
const int g_val = 100;
void test(void)
{
//②局部const变量
const int local_val = 200;
}
- 全局的
const int g_val = 100;:放在 .rodata,硬件只读,绝对不能写。 - 函数内部
const int local_val = 200;:存放在栈上!不是.rodataconst在这里仅仅只是 C 语言编译器的语法检查,不是硬件内存保护。
局部 const 变量只是编译器不让你直接修改。你用指针强转,绕开编译器检查,是可以改写栈上这份内存,不会触发段错误崩溃。
c
const int local_val = 200;
int *p = (int *)&local_val;
*p = 999; // 可以修改成功,程序不会崩溃
重点区分:
.rodata:硬件层面只读,写直接崩溃- 栈上局部 const:仅编译器语法层面只读,指针强转就能篡改

📝检验 3 道题
- 函数里面
const int a = 10;,变量 a 存放在哪个位置? - 全局
const int b = 20;,变量 b 存放在哪个段? - 为什么指针强转修改局部 const 不会段错误,修改字符串字面量就会段错误?
请给出你的答案,有疑问可以一并提出。
:
1.栈上
2.rodata段
3.?
为什么全局变量不能通过指针去强改?
:
为什么全局 const 变量不能指针强转修改
c
const int g_val = 100;
int* p = (int *)&g_val;
*p = 999;
g_val 在 .rodata。 .rodata 虚拟页,页表权限标记为禁止写。 哪怕你用指针强制转换,只能骗过 C 编译器语法检查,骗不过硬件 MMU。 CPU 执行写指令时,MMU 查页表发现这一页没有写权限,直接触发保护异常 → 段错误,程序直接崩溃。
强转 = 修改 C 语言的类型规则,不能改变操作系统内存页的硬件读写权限。

回到第 3 道考题完整参考答案:
字符串字面量存放在
.rodata,内存页硬件标记只读,写操作触发硬件保护异常,发生段错误; 局部const变量在栈内存,栈页本身拥有读写权限,const仅仅是编译器语法限制。指针强转绕开编译检查后,可以正常写栈内存,硬件不会拦截,不会崩溃。
📝巩固 3 道检验题(承接上面内容)
const关键字有哪两层保护,分别是谁提供保护?- 下面哪一段代码运行会发生段错误
c
//A
void fun(){
const int x = 10;
int *q = (int *)&x;
*q = 100;
}
//B
const int y = 20;
int *w = (int *)&y;
*w = 200;
- 能不能说:只要加了
const修饰,就一定放在.rodata?为什么?
:
✅三道全部答对,理解到位。 小结复盘:
- 局部
const:仅编译器语法保护,内存在栈,页可读写,指针强转可篡改。 - 全局
const:编译器 + 硬件双重保护,存.rodata,页表设置只读,强转写直接段错误。
.rodata 在 exe 磁盘文件 和 虚拟内存中的关系
回忆之前学过:
.text、.data:exe 磁盘文件里保存完整真实数据,程序启动,操作系统把磁盘内容拷贝到虚拟内存对应段。
.rodata 同样属于 exe 磁盘文件里面的段。 编译链接完成之后,字符串字面量、全局 const 常量,就已经写进 exe 的.rodata段,保存在硬盘。
程序运行时:操作系统把 exe 的.rodata映射到进程虚拟地址空间。
⚠️注意:这里是映射,不是一定立刻把全部数据拷贝进物理内存。 遵循请求分页机制:只有 CPU 第一次访问这条常量的时候,才会发生缺页异常,操作系统把磁盘上 rodata 内容加载进物理内存,同时给这一页设置只读权限。
对比.bss加深理解:
.rodata:exe 磁盘文件存有真实数据。.bss:exe 磁盘几乎不存数据,仅仅记录需要多大空间,运行时直接填 0。

📝三道检验题
.rodata段的数据,磁盘 exe 文件中是否真实保存?- rodata 的数据是程序一运行就全部复制到物理内存吗?
- 和.bss 对比,rodata 与 bss 在 exe 磁盘文件存储上的核心差别是什么?
写出你的答案,有疑问可以一起问。
:
1.是
2.不是 需要第一次访问并分配物理页框
3..bss只存数据多大,不存具体数值;而rodata存具体数值
rodata到底属于哪一个大段?
:
✅三道回答全部正确。
.rodata 到底属于哪一个大段?
- 在链接后的 exe 可执行文件(目标文件视角)
.text、.rodata经常会被链接器合并到同一个代码段 (Code Segment)。
代码段:放机器指令 + 只读常量。 所以磁盘文件层面:rodata 属于代码段 Code Segment 的一部分。
- 在进程虚拟地址空间(运行内存视角) 虚拟地址空间不再严格区分代码段内部子段。 只是两块虚拟内存页:
- 一部分页:
.text,存放机器指令,只读 + 可执行。 - 另一部分页:
.rodata,存放只读常量,只读、不可执行。
重要考研易错:
.text:可读、可执行,不能写.rodata:只读,不可执行 二者权限不一样!不能认为 rodata 就是 text。链接时磁盘文件可以合并,运行时内存页权限可以分开。

容易混淆的案例
c
char str1[] = "hello";
char *str2 = "hello";
-
char str1[] = "hello";数组,在栈上开辟内存。程序启动把 rodata 里的hello拷贝一份到栈数组。栈内存可读可写,str1[0]='A'合法,不会崩溃。 -
char *str2 = "hello";指针变量str2本身在栈;指针指向的字符串字面量"hello"存放在.rodata。str2[0]='A',写 rodata,段错误崩溃。
核心区分:
[]数组会发生拷贝;*指针直接指向 rodata 原始常量。

📝检验三道题
- 磁盘 exe 文件中,rodata 经常和哪个段合并为代码段?运行时内存二者权限有什么区别?
char str[]="test";,字符串test原始常量放在哪里?运行时 str 数组本体存放在哪里?char* p="test";,指针 p 本身存放在哪里?"test"存放在哪里?
作答,有疑问继续提。
:
1.text text可读可执行;rodata只能读和执行
2.data 栈上
3.栈上,rodata
为什么学习虚拟空间布局的时候没有提到rodata
:
先订正第 1 题小错误:
.rodata:只读,不可执行,不能执行!.text:可读、可执行、禁止写。 CPU 不会把 rodata 里面的数据当成指令跑。
第 2 题订正:原始常量"test"放在 .rodata,不是.data;数组本体 str 在栈上 ✔。 第 3 题答案正确:指针 p 本身在栈,字符串字面量存.rodata ✔。
为什么很多教材讲虚拟地址布局图,不画.rodata?
-
教材简化绘图 经典虚拟内存布局图只画出五大块:
.text、.data、.bss、堆、栈。 很多书籍把 .rodata 归到.text 代码段内部,画图不单独画出。 磁盘目标文件:链接器经常把.text与.rodata合并成代码段。 所以简图直接写代码段(.text),内部包含机器指令 + 只读常量,图上就不单独标出 rodata。 -
考研 408 特点 408 大纲虚拟内存进程布局,不会强制区分 text 与 rodata; 但是 C 语言题目会考字符串常量存放在
.rodata,段错误根源,属于 C 语言 + 操作系统交叉坑。 -
真实 Linux 内存映射 运行程序,
cat /proc/[pid]/maps,你能看到:
- 一块权限
r-x:.text(读、执行) - 一块权限
r--:.rodata(仅读,不能执行) 真实运行环境二者是分开两块虚拟内存,权限不一样。
考试简图省略,真实运行是两个不同内存区域。
一句话总结:画图省略 ≠ 内存不存在。简图把 rodata 藏进代码段,实际运行独立,权限不同。
全局初始化 const 与字符串常量,rodata 细节
c
const int a = 100;
char* s = "abcd";
二者都放在.rodata,但是有一个细节: 只有编译期就能确定的常量,才会放进.rodata。
如果是下面这种:
c
int num = 10;
const int b = num;
b是全局 const,但它的值要运行时才确定,不能放到 rodata,放到.data段。
rodata 存放的是:编译阶段就可以算出来的常量。运行时才得到的值,就算 const 也进不去 rodata。
📝三道检验题
- 运行时环境,
.text与.rodata内存页权限分别是什么? const int b = num;,num 是全局变量,b 也是全局 const,b 能否放入.rodata?为什么?- 为什么很多教科书进程虚拟内存布局图看不到.rodata?
请回答,有疑问继续问。
:
1.可读可写可执行
2.不能 因为b需要执行后才能得到确定的值
3.因为属于text?
rodata属于text吗?还有全局变量const int b 为什么在radata?为什么我记得在data里边存放呢
:
先订正第 1 题
-
.text:可读、可执行、不可写 (r‑x).rodata:只读、不可执行、不可写 (r‑‑) ❌不是可读可写。写这两个段都会触发段错误。 -
你的答案正确,不能放入 rodata,值运行时才确定。
-
第 3 题半对:磁盘 exe 文件链接阶段,可以合并进 text(代码段);但运行时虚拟内存,二者是分开的两块区域,rodata 不属于 text。
rodata 属于.text 吗
- ✅磁盘 exe(链接后):链接器可以把
.text+.rodata合并为「代码段 Code Segment」,只是文件层面的合并。 - ✅程序运行、加载到虚拟内存之后:二者完全分开,不是同一个段
.text:权限r‑x,放机器指令,可以 CPU 执行。.rodata:权限r‑‑,放常量数据,绝对不能执行。
关键:文件上可以合并,内存运行时拆开,权限不一样,运行时 rodata 不属于.text。考试做题区分开。
全局const int b什么时候在.rodata,什么时候在.data
c
//情况A:编译期常量,编译就能算出数值
const int b = 100;
100编译期间就知道,放在 .rodata,硬件只读。
c
//情况B:值运行时才可以算出
int num = 20;
const int b = num;
num是变量,程序跑起来才知道 num 的值。编译器编译阶段拿不到 b 的数值。 👉此时 b 是全局 const,存放在 .data 段! .data段页面权限是可读可写。
这里注意:虽然加了 const,只是 C 编译器语法不让直接修改。你指针强转写这个变量,不会段错误,可以改写成功,因为所在页硬件允许写。
总结表格
表格
| 定义 | 存放段 | 硬件页权限 | 指针强转写的结果 |
|---|---|---|---|
const int b=100; | .rodata | 只读 (r‑‑) | 段错误,崩溃 |
int num=20; const int b=num; | .data | 可读可写 | 修改成功,无崩溃 |
你记忆的 “有的全局 const 在.data” 是正确的,前提:初始值不是编译期常量。 不是写了 const 就一定进 rodata,硬性条件:编译期就能确定最终数值。
rodata 还有什么东西放进去
除了字符串字面量、编译期全局 const。
- 部分编译器下的浮点数常量,编译期确定,放进
.rodata - 跳转表、常量数组,
const int arr[]={1,2,3}全局,编译期确定,存.rodata
局部的
const int arr[]={1,2,3}:数组本体在栈,运行时把常量拷贝到栈。
c
const int arr[] = {1,2,3}; //全局,编译确定 → rodata
void test(){
const int arr2[] = {1,2,3}; //局部,数组在栈上
}

📝三道检验题
- 运行状态下,
.text和.rodata的页权限分别是什么? int x=5; const int c = x;,全局 c 存放在哪个段?指针强转修改会不会段错误?为什么?- 全局
const int arr[]={1,3,5};存放在哪里?局部const int arr2[]={1,3,5};数组本体存放在哪里?
作答,有疑问继续抛出。
:
1.可读可执行 不可读不可执行
2.data 不会 因为data段可以修改
3.rodata 栈上
:
✅整体完成度很高,订正第 1 题的小笔误:
.text:可读、可执行、不可写(r‑x).rodata:只读、不可执行、不可写(r‑‑)
rodata 是可读,不是不可读。
2、3 两题全部答对。 复盘第二题关键点:虽然变量带const修饰,但存放在.data,内存页硬件拥有读写权限;const仅编译器语法检查,指针强转绕过后就可以改写,不会触发段错误。
rodata 相关的易错综合辨析
把前面全部知识收拢,做对比总结。
记住一条铁律:是否进入.rodata,看两点:①全局作用域;②初始值编译期即可确定。两者缺一不可。
- 局部
const,无论值是不是常量,全部在栈,永远不会进.rodata。
c
void test(){
const int a = 100; //局部,栈,不是rodata
}
- 全局,但是初值运行时才可以算出来,进
.data,不进.rodata。
c
int m = 10;
const int b = m; //全局,但初值运行时确定 → .data
- 全局 + 编译期常量 → 进入
.rodata,硬件只读。
c
const int c = 100; //全局,编译就能确定100 → rodata
- 字符串字面量
"abc",无论写在函数内部还是全局位置,字面量本体永远放在.rodata。
c
void test(){
char* p = "abc";
// p在栈;"abc"这个字符串本体,rodata
}
极易踩坑对比代码
c
char arr1[] = "hello"; //数组,栈上开辟空间,拷贝rodata的hello;arr1可修改
char* arr2 = "hello"; //指针,栈存指针;直接指向rodata的hello;写arr2段错误
📝三道检验题
- 局部
const int x = 500;,x 能否存放到 rodata?为什么? - 全局
const double pi = 3.1415;,pi 存放在哪个段?指针强转修改会发生什么? void fun(){ char* s = "linux"; char buf[]="linux"; },说出"linux"字面量、s、buf 分别存储的位置。
作答,有疑问直接问。
:
1.不能 因为在栈上
2.rodata段 会报错
3.“linux”在rodata s、buf在栈上
请你列举出各个段的权限
:
Linux 进程虚拟内存各段权限汇总(运行时虚拟页)
表格
| 段名 | 存储内容 | 页权限 (r‑w‑x) | 核心特点 |
|---|---|---|---|
| .text | 机器指令、程序代码 | r‑x 可读、不可写、可执行 | 唯一具备执行权限的段;写入触发段错误 |
| .rodata | 字符串字面量、编译期常量全局const | r‑‑ 可读、不可写、不可执行 | 纯只读,不能当作指令执行;写直接段错误 |
| .data | 已初始化全局变量、static 局部变量(有初值) | rw‑ 可读、可写、不可执行 | 可以正常读写修改 |
| .bss | 未初始化全局变量、未初始化 static 变量 | rw‑ 可读、可写、不可执行 | 程序启动由操作系统自动清零 |
| 堆 (heap) | malloc/realloc动态分配内存 | rw‑ 可读、可写、不可执行 | 向上增长,手动 free 释放 |
| 栈 (stack) | 局部变量、栈帧、返回地址、函数参数 | rw‑ 可读、可写、不可执行 | 向下生长,函数调用自动开辟销毁 |
权限符号说明: r = read 读;w = write 写;x = execute 执行;
‑代表没有该权限
📝三道检验题
- 哪一个段具备执行权限?
.rodata能否执行代码? - 向
.bss写入数据会不会发生段错误?理由? .data、.bss、栈、堆,权限全部一样吗?
写出你的答案,有疑问随时提出。
:
1.text
2.不会 因为可写
3.一样
:
✅全部答对。 复盘重点:
- 整个进程虚拟地址空间,只有
.text拥有 x 执行权限;rodata 只有读权限,不能执行指令。 .bss权限rw‑可读可写,允许修改,不会段错误,只是程序刚启动时 OS 帮它全部置 0。.data/.bss/ 堆 / 栈 权限全部是rw‑,权限完全一致,只是存放的数据、生命周期、内存来源不一样。
区分磁盘 exe 文件 VS 运行时内存
表格
| 段名 | 磁盘 exe 文件是否保存真实数据 | 加载到虚拟内存后 |
|---|---|---|
| .text | 保存机器指令 | 映射,请求分页加载,权限r‑x |
| .rodata | 保存常量真实数据 | 映射,请求分页加载,权限r‑‑ |
| .data | 保存初始化变量真实值 | 映射,请求分页加载,权限rw‑ |
| .bss | 不存实际数据,只记录需要的空间大小 | 运行时开辟空间,操作系统清零,权限rw‑ |
| 堆 | exe 磁盘文件不存在堆 | 进程运行之后 OS 新开辟,malloc使用 |
| 栈 | exe 磁盘文件不存在栈 | 进程运行之后 OS 新开辟,函数调用使用 |
关键记忆:堆、栈,磁盘 exe 里面根本没有,是程序跑起来操作系统才创建出来。
📝三道检验题
- 磁盘 exe 里面有没有栈和堆?
.bss在 exe 磁盘文件存储什么内容?运行时操作系统做什么操作?.rodata和.bss对比,磁盘上最大区别是什么?
作答,有不懂的地方直接提问。
:
1.没有
2.只记数据大小 开辟空间
3. .bss只存数据大小 而.rodata存具体数
:
全部知识点总结
- 存放内容:字符串字面量、编译期就能确定数值的全局 const 常量、全局 const 常量数组。
- 磁盘 exe:存在真实数据;链接阶段可以和
.text合并为代码段。 - 运行内存:二者分开。
.text:r‑x可读可执行不可写.rodata:r‑‑只读,不可写、不可执行
- const 两层保护
- 局部
const:仅编译器语法检查,变量在栈,页权限rw‑,指针强转可以篡改。 - 全局编译期
const:编译器 + 硬件双重保护,存.rodata,写直接段错误。 - 全局但运行时才能确定初值的
const,存入.data,硬件允许写,指针强转可以修改。
- 局部
char arr[]="hello"vschar*p="hello"char arr[]="hello":栈上开辟数组,把.rodata 字符串拷贝过来,可以修改。char* p="hello":p 存栈,直接指向.rodata 的字符串;尝试写触发段错误。
- 加载方式:请求分页,第一次访问发生缺页,才把磁盘内容载入物理内存。
- 和
.bss区别:.rodata磁盘保存真实常量;.bss磁盘仅记录大小,运行时清零。
📝最终综合检验三道题
- 全局
const int a = 10;与局部const int b =10;,二者底层有什么本质区别(存储位置、保护方式、指针强转后果)? const int num = get();全局变量,num 存放在哪个段?指针强转修改会不会段错误?为什么?- 简述为什么
.rodata不具备执行权限,而.text要有执行权限?
作答,全部做完,.rodata知识点就彻底吃透。有任何疑问都可以问。
:
1.a存在与rodata 由编译器和硬件保护 强转报错;b在栈上,受编译器保护,可以通过指针强转
2.data 不会 因为data允许修改
3.?
:
第 1、2 题回答正确。
第 3 题参考答案
- .text 存放机器指令,CPU 需要把内存里面的二进制数据当作指令来译码执行,所以必须赋予执行
x权限。 - .rodata 只存放常量数据(字符串、常量数值),不是 CPU 指令,这些内容只用来读取,不需要被 CPU 执行。
- 不给 rodata 执行权限是一种内存保护手段:防止漏洞攻击者,把恶意代码放在常量数据里,哄骗 CPU 去执行,提升程序安全性。
一句话:
.text存指令,需要跑;.rodata存纯数据,只供读,不需要跑。

2110

被折叠的 条评论
为什么被折叠?



