可以修改C中的字符串文字吗?

时间:2019-06-24 20:59:31

标签: c gcc x86-16

我最近有一个问题,我知道一个指向常量数组的指针按下面的代码进行初始化,它位于.rodata区域中,并且该区域仅可读。 但是,我在模式C11中看到,此内存地址行为的写入将是不确定的。 我知道Borland的Turbo-C编译器可以在指针指向的地方写,这是因为处理器在当时的某些系统上以实模式运行,例如MS-DOS?还是与处理器的操作模式无关?在保护模式下使用处理器,是否还有其他编译器可以写入指针并且不会导致任何内存破坏失败?

#include <stdio.h>

int main(void) {
    char *st = "aaa";
    *st = 'b'; 
    return 0;
}

在此代码中,使用Turbo-C在MS-DOS中进行编译,您将能够写入内存

5 个答案:

答案 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"; }将返回不同的指针值,而不是合并相同的字符串文字。


相关: