C语言中如何实现只读存储器(ROM)?

31

我听说在C语言中,如果我执行以下代码:

char *s = "hello world". 

“hello world”实际上存储在只读内存中。

我对只读内存不太清楚,什么是解释?这是否像一个标志,告诉编译器不要写入那个部分?


1
你有参考资料吗?我认为你的意思是:const char* s = "hello world"。 - James Black
3
并不清楚所有处理器架构都支持受保护的内存。 - jldupont
1
@James Black:显然,OP在谈论字符串字面量“hello world”,无论指针如何声明,它都可以存储在只读内存中。 - AnT stands with Russia
我的参考链接是:https://dev59.com/NXI-5IYBdhLWcg3wu7FU请注意,这是一段关于编程的内容。 - user188276
我也很好奇当我们声明一个常量时,例如:const int a; 是否将a分配到只读内存部分?(因为它是常量,不可修改,所以我认为是这样的) - user188276
7个回答

43
这不是C语言的一个特性,而是编译器/链接器和操作系统共同作用的结果。当你编译这样的代码时,会发生以下情况:编译器将字符串放入只读数据段中;链接器收集所有这些只读段中的数据,并将它们放入单个段中,该段位于可执行文件中,并标有“只读”属性;现在操作系统可执行文件加载器开始工作,它加载可执行文件(或更确切地说,将其映射到内存中)。一旦完成,加载器遍历各个段并为每个段设置访问权限。对于只读数据段,它很可能会禁用代码执行和写访问权限。代码(例如,函数)具有执行权限但没有写访问权限。普通数据如静态变量具有读取和写入权限等等...这就是现代操作系统的做法。正如所说,这不是C语言的一个特性。例如,如果你为DOS编译相同的问题,程序将运行,但不可能进行写保护,因为DOS加载器不知道只读段。

2
常量变量是否也和“Hello World”放在同一个部分?(例如:const int a = 6) - user188276
谢谢你的回答。 @Stephen C,加载器是操作系统的一部分吗? - user188276
1
还有,请记住,常量甚至可能会出现在ROM中。(在嵌入式程序的情况下。) - Prof. Falken
1
@Nils Pipenbrinck 有没有可能强制编译器将整个程序放入可写内存中? - phimuemue
我应该指出,许多平台将只读数据部分放置在读取和执行段中以节省空间,但一些链接器(如gold)提供了选项(--rosegment)来更改此设置,或者可以使用链接器脚本。 - Molly Stewart-Gallus
显示剩余3条评论

8

可执行文件包含两部分:一个 .data 节段,包含全局变量;一个 .text 节段,包含实际的机器代码。

字符串被放置在 .data 节段内。当 C 语言看到 "Hello world" 时,它会将字符串 "Hello world" 放入可执行文件本身,并用该字符串加载到的地址替换程序中所有的 "Hello world" 实例。

话虽如此,我不确定为什么它是只读的 - 理论上,一个程序应该能够修改自己的内存。


3
并非所有的处理器和操作系统都支持自修改代码。事实上,大多数现代操作系统都包含保护措施来防止自修改代码,以提高安全性。 - Crashworks
1
字符串字面值因为它们不需要被修改,所以经常被存储在文本段中。 - caf

6

真正的只读存储器由操作系统的存储器子系统实现。操作系统可以将某些页面标记为只读。

在二进制文件中,编译器可以告诉操作系统哪些可执行文件部分应该放在只读内存页上,哪些应该放在可读可写内存页上。


2
嗯...真正的内存保护是在处理器级别实现的。 - jldupont
@jldupont:内存保护确实是在硬件层面上实现的(至少在x86上是这样),但初始设置是由操作系统完成的,也就是说,操作系统会将只读页面标记为只读,并且硬件会执行操作系统设置的只读标记。 - AnT stands with Russia
@AndreyT:当然……我的意思是针对@R Samuel。在没有硬件辅助的情况下,软件层面能做的事情是有限的。 - jldupont
@R - 实际上,在嵌入式系统中,只读存储器可能会使用ROM硬件来实现;例如使用EPROM芯片。 - Stephen C

5
当你写 char s[10]="sneha" 时,你在你的目标文件中分配了10个字节的存储空间(不是内存,在执行程序时才会涉及到内存)。这是静态内存分配(在编译时)。
但是当你写 char *s="sneha"; 时,你没有为存储 "sneha" 分配任何存储空间。它将被存储在只读部分。但是指针 s 存储在不同的部分,取决于它声明的位置。但是它指向只读数据 "sneha"。因此,如果你尝试对它进行写入操作,你将得到分段错误。
例如:
char *s = "sneha";
s[1] = 'N'; 
printf("%s",s);  // you expecting output sNeha, 
                 // but you get a seg fault since it is READ ONLY DATA 

4
在Linux中实现此操作的示例可参考Mark Mitchell、Jeffrey Olham和Alex Samuel的《Advanced Linux Programming》第179页(链接)

1
你可以尝试类似这样的代码:
s[4] = '0';

并查看当您调用时是否显示“hello w0rld”

puts(s);

如果它导致立即的分段错误或数据执行预防异常, 那么它可能是只读的。(如果系统让你这样做, 这并不意味着这是一个好主意。)

1

正如其他人所提到的,常量字符串的内容是否存储在只读内存中取决于操作系统、编译器和芯片架构。

更准确地说,C标准规定引用字符串被视为“const char []”类型(或类似的措辞,我手头没有标准)。

任何试图修改这种字符串内容的代码都会导致未定义行为。这意味着在那一点上可以发生任何事情,编译器的提供者甚至不需要记录可能发生的事情。

实际上,这意味着想要具有可移植性的C或C++程序必须避免修改常量字符串。

通常情况下,编译器不允许您修改“const”变量的内容,因此在大多数情况下可以将“const”视为“只读”。不幸的是,char *和const char *有一个特殊的例外,主要是出于历史原因。这意味着像这样的代码:

char *x = "Hello, World";
*x = 'h';

即使调用未定义的行为,也将无错误或警告编译。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接