我最近有一个问题,我知道一个指向常量数组的指针按下面的代码进行初始化,它位于.rodata
区域中,并且该区域仅可读。
但是,我在模式C11中看到,此内存地址行为的写入将是不确定的。
我知道Borland的Turbo-C编译器可以在指针指向的地方写,这是因为处理器在当时的某些系统上以实模式运行,例如MS-DOS?还是与处理器的操作模式无关?在保护模式下使用处理器,是否还有其他编译器可以写入指针并且不会导致任何内存破坏失败?
#include <stdio.h>
int main(void) {
char *st = "aaa";
*st = 'b';
return 0;
}
在此代码中,使用Turbo-C在MS-DOS中进行编译,您将能够写入内存
答案 0 :(得分:5)
正如已经指出的那样,尝试在C中修改常量字符串会导致未定义的行为。造成这种情况的原因有很多。
一个原因是该字符串可能被放置在只读存储器中。这允许它在同一程序的多个实例之间共享,并且如果页面所在的页面被调出,则不需要将内存保存到磁盘(因为该页面是只读的,因此可以稍后从该页面重新加载)。可执行文件)。如果尝试修改运行时错误,则还可以通过给出错误(例如分段错误)来帮助检测运行时错误。
另一个原因是可以共享字符串。许多编译器(例如gcc
)会注意到同一文字字符串何时在一个编译单元中出现多次,并将为其共享相同的存储空间。因此,如果程序修改了一个实例,也可能会影响其他实例。
也永远不需要这样做,因为通过使用静态字符数组可以轻松实现相同的预期效果。例如:
#include <stdio.h>
int main(void) {
static char st_arr[] = "aaa";
char *st = st_arr;
*st = 'b';
return 0;
}
这与发布的代码试图执行的操作完全相同,但是没有任何未定义的行为。它还占用相同数量的内存。在此示例中,字符串"aaa"
用作数组初始化器,并且没有任何存储空间。数组st_arr
代替了原始示例中的常量字符串,但是(1)它不会被放置在只读内存中,并且(2)它不会与对该字符串的任何其他引用共享。因此,实际上就是您想要的,对其进行修改是安全的。
答案 1 :(得分:3)
您的文字“ aaa”在匿名位置生成一个包含四个const char'a','a','a','\ 0'的静态数组,并返回指向第一个'a'的指针,并将其转换为char *。
尝试修改四个字符中的任何一个都是未定义的行为。未定义的行为可以做任何事情,包括按预期修改char,假装修改char,什么都不做或崩溃。
它与静态const char匿名[4] = {''a','a','a','\ 0'}基本相同。 char * st =(char *)&anonymous [0];
答案 2 :(得分:3)
您正在询问平台是否可能导致未定义的行为被定义。这个问题的答案是肯定的。
但是您也正在询问平台是否定义了此行为。实际上不是。
在一些优化提示下,编译器将合并字符串常量,以便写入一个常量将写入该常量的其他用法。我曾经使用过这个编译器,它具有合并字符串的能力。
请勿编写此代码。这样不好。当您迁移到更现代的平台时,您会后悔以这种风格编写代码。
答案 3 :(得分:3)
要添加到上面的正确答案中,DOS在实模式下运行,因此没有只读内存。所有内存都是平坦且可写的。因此,当时对字面量的写入已得到很好的定义(就像在任何类型的const变量中一样)。
答案 4 :(得分:2)
在保护模式下使用处理器,是否还有其他编译器可以写入指针并且不会发生任何内存破坏故障?
How can some GCC compilers modify a constant char pointer?
根据{{3}}, GCC 3和更早版本曾经支持gcc -fwriteable-strings
,以便您在显然合法的地方编译旧的K&RC。 (这是ISO C中未定义的行为,因此是ISO C程序中的错误)。该选项将定义ISO C未定义的分配行为。
https://gcc.gnu.org/onlinedocs/gcc-3.3.6/gcc/Incompatibilities.html
-fwritable-strings
将字符串常量存储在可写数据段中,不要使它们唯一化。这是为了与假定它们可以写入字符串常量的旧程序兼容。写入字符串常量是一个非常糟糕的主意; “常量”应该是常量。
GCC 4.0删除了该选项(GCC 3.3.6 manual - C Dialect options);最新的GCC3系列是2006年3月的gcc3.4.6。尽管在该版本中显然是release notes。
gcc -fwritable-strings
会将字符串文字视为非常量匿名字符数组(请参阅@gnasher的答案),因此它们进入.data
部分而不是.rodata
,因此被链接到可执行文件的一部分,映射到读写页,而不是只读的。 (可执行段基本上与x86分段无关,这只是从可执行文件到内存的开始+范围内存映射。)
这将禁用重复字符串合并,因此char *foo() { return "hello"; }
和char *bar() { return "hello"; }
将返回不同的指针值,而不是合并相同的字符串文字。
相关: