学习记录与.rodata

目录

前言

.rodata 只读段(Read‑Only Data)

与const修饰的变量

为什么全局 const 变量不能指针强转修改

.rodata 在 exe 磁盘文件 和 虚拟内存中的关系

.rodata 到底属于哪一个大段?

容易混淆的案例

为什么很多教材讲虚拟地址布局图,不画.rodata?

全局初始化 const 与字符串常量,rodata 细节

rodata 属于.text 吗

全局const int b什么时候在.rodata,什么时候在.data

rodata 还有什么东西放进去

rodata 相关的易错综合辨析

极易踩坑对比代码

Linux 进程虚拟内存各段权限汇总(运行时虚拟页)

区分磁盘 exe 文件 VS 运行时内存

全部知识点总结


前言

虽然是我只是记录学习,但是这个CSDN文章评分真是太怪了。上一篇没采用问答式写法,掺了点自己看法,结果评分还没有问答高()。这篇就还是以问答式记录吧。


.rodata 只读段(Read‑Only Data)

前置回忆:.text段存放程序机器指令;.data存已经初始化的全局变量;.bss存放未初始化全局变量。

.rodata 是只读数据段,属于可执行程序 exe 磁盘文件中的段,程序加载时映射到进程虚拟地址空间。专门存放不会被修改的常量数据 **。**

最典型例子:字符串字面量。

c

char *p = "hello world";

"hello world"这个字符串常量,编译的时候就被放进.rodata段。 它不在栈,不在堆,不在.data,放在.rodata

⚠️关键:这块内存硬件层面标记为只读,CPU 不允许写操作。试图修改它,程序直接崩溃(段错误)。


📝检验问题(3 题,请你回答)

  1. .rodata里面存放什么类型的数据?
  2. char *p="hello";,字符串"hello"存放在哪个段?
  3. 尝试修改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; 
}
  1. 全局的const int g_val = 100;:放在 .rodata,硬件只读,绝对不能写。
  2. 函数内部const int local_val = 200;存放在栈上!不是.rodata const在这里仅仅只是 C 语言编译器的语法检查,不是硬件内存保护

局部 const 变量只是编译器不让你直接修改。你用指针强转,绕开编译器检查,是可以改写栈上这份内存,不会触发段错误崩溃

c

const int local_val = 200;
int *p = (int *)&local_val;
*p = 999;   // 可以修改成功,程序不会崩溃

重点区分:

  • .rodata硬件层面只读,写直接崩溃
  • 栈上局部 const:仅编译器语法层面只读,指针强转就能篡改


📝检验 3 道题

  1. 函数里面 const int a = 10;,变量 a 存放在哪个位置?
  2. 全局 const int b = 20;,变量 b 存放在哪个段?
  3. 为什么指针强转修改局部 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 道检验题(承接上面内容)

  1. const关键字有哪两层保护,分别是谁提供保护?
  2. 下面哪一段代码运行会发生段错误

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;
  1. 能不能说:只要加了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。


📝三道检验题

  1. .rodata段的数据,磁盘 exe 文件中是否真实保存?
  2. rodata 的数据是程序一运行就全部复制到物理内存吗?
  3. 和.bss 对比,rodata 与 bss 在 exe 磁盘文件存储上的核心差别是什么?

写出你的答案,有疑问可以一起问。

1.是

2.不是 需要第一次访问并分配物理页框

3..bss只存数据多大,不存具体数值;而rodata存具体数值

rodata到底属于哪一个大段?

✅三道回答全部正确。

.rodata 到底属于哪一个大段?

  1. 链接后的 exe 可执行文件(目标文件视角) .text.rodata 经常会被链接器合并到同一个代码段 (Code Segment)

代码段:放机器指令 + 只读常量。 所以磁盘文件层面:rodata 属于代码段 Code Segment 的一部分

  1. 进程虚拟地址空间(运行内存视角) 虚拟地址空间不再严格区分代码段内部子段。 只是两块虚拟内存页:
  • 一部分页:.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"存放在.rodatastr2[0]='A',写 rodata,段错误崩溃。

核心区分:[]数组会发生拷贝;*指针直接指向 rodata 原始常量。


📝检验三道题

  1. 磁盘 exe 文件中,rodata 经常和哪个段合并为代码段?运行时内存二者权限有什么区别?
  2. char str[]="test";,字符串test原始常量放在哪里?运行时 str 数组本体存放在哪里?
  3. 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?

  1. 教材简化绘图 经典虚拟内存布局图只画出五大块:.text.data.bss、堆、栈。 很多书籍把 .rodata 归到.text 代码段内部,画图不单独画出。 磁盘目标文件:链接器经常把.text.rodata合并成代码段。 所以简图直接写代码段(.text),内部包含机器指令 + 只读常量,图上就不单独标出 rodata。

  2. 考研 408 特点 408 大纲虚拟内存进程布局,不会强制区分 text 与 rodata; 但是 C 语言题目会考字符串常量存放在.rodata,段错误根源,属于 C 语言 + 操作系统交叉坑。

  3. 真实 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。


📝三道检验题

  1. 运行时环境,.text.rodata内存页权限分别是什么?
  2. const int b = num;,num 是全局变量,b 也是全局 const,b 能否放入.rodata?为什么?
  3. 为什么很多教科书进程虚拟内存布局图看不到.rodata?

请回答,有疑问继续问。

1.可读可写可执行

2.不能 因为b需要执行后才能得到确定的值

3.因为属于text?

rodata属于text吗?还有全局变量const int b 为什么在radata?为什么我记得在data里边存放呢

先订正第 1 题

  1. .text可读、可执行、不可写 (r‑x) .rodata只读、不可执行、不可写 (r‑‑) ❌不是可读可写。写这两个段都会触发段错误。

  2. 你的答案正确,不能放入 rodata,值运行时才确定。

  3. 第 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。

  1. 部分编译器下的浮点数常量,编译期确定,放进.rodata
  2. 跳转表、常量数组,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}; //局部,数组在栈上
}


📝三道检验题

  1. 运行状态下,.text.rodata的页权限分别是什么?
  2. int x=5; const int c = x;,全局 c 存放在哪个段?指针强转修改会不会段错误?为什么?
  3. 全局const int arr[]={1,3,5};存放在哪里?局部const int arr2[]={1,3,5};数组本体存放在哪里?

作答,有疑问继续抛出。

1.可读可执行 不可读不可执行

2.data 不会 因为data段可以修改

3.rodata 栈上

✅整体完成度很高,订正第 1 题的小笔误:

  1. .text可读、可执行、不可写(r‑x) .rodata只读、不可执行、不可写(r‑‑)

rodata 是可读,不是不可读。

2、3 两题全部答对。 复盘第二题关键点:虽然变量带const修饰,但存放在.data,内存页硬件拥有读写权限;const仅编译器语法检查,指针强转绕过后就可以改写,不会触发段错误。

rodata 相关的易错综合辨析

把前面全部知识收拢,做对比总结。

记住一条铁律:是否进入.rodata,看两点:①全局作用域;②初始值编译期即可确定。两者缺一不可。

  1. 局部const,无论值是不是常量,全部在栈,永远不会进.rodata

c

void test(){
    const int a = 100; //局部,栈,不是rodata
}
  1. 全局,但是初值运行时才可以算出来,进.data,不进.rodata

c

int m = 10;
const int b = m; //全局,但初值运行时确定 → .data
  1. 全局 + 编译期常量 → 进入.rodata,硬件只读。

c

const int c = 100; //全局,编译就能确定100 → rodata
  1. 字符串字面量"abc",无论写在函数内部还是全局位置,字面量本体永远放在.rodata

c

void test(){
    char* p = "abc"; 
    // p在栈;"abc"这个字符串本体,rodata
}

极易踩坑对比代码

c

char arr1[] = "hello"; //数组,栈上开辟空间,拷贝rodata的hello;arr1可修改
char* arr2  = "hello"; //指针,栈存指针;直接指向rodata的hello;写arr2段错误

📝三道检验题

  1. 局部const int x = 500;,x 能否存放到 rodata?为什么?
  2. 全局 const double pi = 3.1415;,pi 存放在哪个段?指针强转修改会发生什么?
  3. 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字符串字面量、编译期常量全局constr‑‑ 可读、不可写、不可执行纯只读,不能当作指令执行;写直接段错误
.data已初始化全局变量、static 局部变量(有初值)rw‑ 可读、可写、不可执行可以正常读写修改
.bss未初始化全局变量、未初始化 static 变量rw‑ 可读、可写、不可执行程序启动由操作系统自动清零
堆 (heap)malloc/realloc动态分配内存rw‑ 可读、可写、不可执行向上增长,手动 free 释放
栈 (stack)局部变量、栈帧、返回地址、函数参数rw‑ 可读、可写、不可执行向下生长,函数调用自动开辟销毁

权限符号说明: r = read 读;w = write 写;x = execute 执行;代表没有该权限


📝三道检验题

  1. 哪一个段具备执行权限?.rodata能否执行代码?
  2. .bss写入数据会不会发生段错误?理由?
  3. .data.bss、栈、堆,权限全部一样吗?

写出你的答案,有疑问随时提出。

1.text

2.不会 因为可写

3.一样

✅全部答对。 复盘重点:

  1. 整个进程虚拟地址空间,只有 .text 拥有 x 执行权限;rodata 只有读权限,不能执行指令。
  2. .bss权限rw‑可读可写,允许修改,不会段错误,只是程序刚启动时 OS 帮它全部置 0。
  3. .data / .bss / 堆 / 栈 权限全部是rw‑,权限完全一致,只是存放的数据、生命周期、内存来源不一样。

区分磁盘 exe 文件 VS 运行时内存

表格

段名磁盘 exe 文件是否保存真实数据加载到虚拟内存后
.text保存机器指令映射,请求分页加载,权限r‑x
.rodata保存常量真实数据映射,请求分页加载,权限r‑‑
.data保存初始化变量真实值映射,请求分页加载,权限rw‑
.bss不存实际数据,只记录需要的空间大小运行时开辟空间,操作系统清零,权限rw‑
exe 磁盘文件不存在堆进程运行之后 OS 新开辟,malloc使用
exe 磁盘文件不存在栈进程运行之后 OS 新开辟,函数调用使用

关键记忆:堆、栈,磁盘 exe 里面根本没有,是程序跑起来操作系统才创建出来。


📝三道检验题

  1. 磁盘 exe 里面有没有栈和堆?
  2. .bss在 exe 磁盘文件存储什么内容?运行时操作系统做什么操作?
  3. .rodata.bss对比,磁盘上最大区别是什么?

作答,有不懂的地方直接提问。

1.没有

2.只记数据大小 开辟空间

3. .bss只存数据大小 而.rodata存具体数

全部知识点总结

  1. 存放内容:字符串字面量、编译期就能确定数值的全局 const 常量、全局 const 常量数组。
  2. 磁盘 exe:存在真实数据;链接阶段可以和.text合并为代码段。
  3. 运行内存:二者分开。
    • .textr‑x可读可执行不可写
    • .rodatar‑‑只读,不可写、不可执行
  4. const 两层保护
    • 局部const:仅编译器语法检查,变量在栈,页权限rw‑,指针强转可以篡改。
    • 全局编译期const:编译器 + 硬件双重保护,存.rodata,写直接段错误。
    • 全局但运行时才能确定初值的const,存入.data,硬件允许写,指针强转可以修改。
  5. char arr[]="hello" vs char*p="hello"
    • char arr[]="hello":栈上开辟数组,把.rodata 字符串拷贝过来,可以修改。
    • char* p="hello":p 存栈,直接指向.rodata 的字符串;尝试写触发段错误。
  6. 加载方式:请求分页,第一次访问发生缺页,才把磁盘内容载入物理内存。
  7. .bss区别:.rodata磁盘保存真实常量;.bss磁盘仅记录大小,运行时清零。

📝最终综合检验三道题

  1. 全局const int a = 10;与局部const int b =10;,二者底层有什么本质区别(存储位置、保护方式、指针强转后果)?
  2. const int num = get();全局变量,num 存放在哪个段?指针强转修改会不会段错误?为什么?
  3. 简述为什么.rodata不具备执行权限,而.text要有执行权限?

作答,全部做完,.rodata知识点就彻底吃透。有任何疑问都可以问。

1.a存在与rodata 由编译器和硬件保护 强转报错;b在栈上,受编译器保护,可以通过指针强转

2.data 不会 因为data允许修改

3.?

第 1、2 题回答正确。

第 3 题参考答案

  1. .text 存放机器指令,CPU 需要把内存里面的二进制数据当作指令来译码执行,所以必须赋予执行x权限。
  2. .rodata 只存放常量数据(字符串、常量数值),不是 CPU 指令,这些内容只用来读取,不需要被 CPU 执行。
  3. 不给 rodata 执行权限是一种内存保护手段:防止漏洞攻击者,把恶意代码放在常量数据里,哄骗 CPU 去执行,提升程序安全性。

一句话:.text存指令,需要跑;.rodata存纯数据,只供读,不需要跑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值