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

Yur*_*que 4 c gcc x86-16

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

#include <stdio.h>

int main(void) {
    char *st = "aaa";
    *st = 'b'; 
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在此代码与MS-DOS中的Turbo-C一起编译时,您将能够写入内存

Pet*_*des 7

\n

是否有其他编译器可以写入指针并且在保护模式下使用处理器时不会发生任何内存泄露故障?

\n
\n

gcc -fwriteable-strings根据https://gcc.gnu.org/onlinedocs/gcc-3.3.6/gcc/Incompatibilities.html,GCC 3 及更早版本曾经支持让您编译旧的 K&R C,这显然是合法的。(这是 ISO C 中未定义的行为,因此是 ISO C 程序中的错误)。该选项将定义 ISO C 未定义的赋值行为。

\n
\n

GCC 3.3.6 手册 - C 方言选项

\n

-fwritable-strings
\n将字符串常量存储在可写数据段中,并且不要对它们进行唯一化。这是为了与旧程序兼容,旧程序假设它们可以写入字符串常量。

\n

写入字符串常量是一个非常糟糕的主意;\xe2\x80\x9cconstants\xe2\x80\x9d 应该是常量。

\n
\n

GCC 4.0 删除了该选项(发行说明);最后一个 GCC3 系列是 2006 年 3 月的 gcc3.4.6。尽管显然它在该版本中已经出现了错误

\n

gcc -fwritable-strings会将字符串文字视为非常量匿名字符数组(请参阅@gnasher\'s 答案),因此它们进入该.data部分而不是.rodata,从而链接到映射到读+写页面的可执行文件的一段,不是只读的。(可执行段基本上与 x86 分段无关,它只是从可执行文件到内存的起始+范围内存映射。)

\n

并且它将禁用重复字符串合并,因此char *foo() { return "hello"; }andchar *bar() { return "hello"; }将返回不同的指针值,而不是合并相同的字符串文字。

\n
\n

有关的:

\n\n
\n

链接器选项:仍然是未定义的行为,因此可能不可行

\n

在 GNU/Linux 上,使用ld -N( --omagic) 链接将使文本(以及数据)部分读+写。这可能适用于.rodata即使现代 GNU Binutilsld放入.rodata其自己的部分(通常具有读取但不具有执行权限)而不是使其成为 .text. 拥有.text写很容易成为一个安全问题:您永远不希望页面同时具有 write+exec,否则某些错误(例如缓冲区溢出)可能会变成代码注入攻击。

\n

要从 gcc 执行此操作,请gcc -Wl,-N在链接时将该选项传递给 ld。

\n

这对于写入对象的未定义行为没有任何作用const 例如,编译器仍然会合并重复的字符串,因此写入一个字符串char *foo = "hello";将影响"hello"整个程序中的所有其他使用,甚至跨文件。

\n
\n

用什么代替:

\n

如果您想要可写的内容,请使用static char foo[] = "hello";带引号的字符串只是非常量数组的数组初始值设定项。 额外的好处是,这比static char *foo = "hello";全局范围更有效,因为获取数据的间接级别少了一层:它只是一个数组,而不是存储在内存中的指针。

\n


Tom*_*zes 5

As has been pointed out, trying to modify a constant string in C results in undefined behavior. There are several reasons for this.

One reason is that the string may be placed in read-only memory. This allows it to be shared across multiple instances of the same program, and doesn't require the memory to be saved to disk if the page it's on is paged out (since the page is read-only and thus can be reloaded later from the executable). It also helps detect run-time errors by giving an error (e.g. a segmentation fault) if an attempt is made to modify it.

Another reason is that the string may be shared. Many compilers (e.g., gcc) will notice when the same literal string appears more than once in a compilation unit, and will share the same storage for it. So if a program modifies one instance, it could affect others as well.

There is also never a need to do this, since the same intended effect can easily be achieved by using a static character array. For instance:

#include <stdio.h>

int main(void) {
    static char st_arr[] = "aaa";
    char *st = st_arr;
    *st = 'b'; 
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

This does exactly what the posted code attempted to do, but without any undefined behavior. It also takes the same amount of memory. In this example, the string "aaa" is used as an array initializer, and does not have any storage of its own. The array st_arr takes the place of the constant string from the original example, but (1) it will not be placed in read-only memory, and (2) it will not be shared with any other references to the string. So it's safe to modify it, if in fact that's what you want.

  • @YuriAlbuquerque 糟糕,我遗漏了 `static` 关键字 - 我刚刚添加了它。对于那个很抱歉! (2认同)