在C++中创建可修改的字符串字面量

5
在C++中是否可能创建一个可修改的字符串字面值?例如:
char* foo[] = {
    "foo",
    "foo"
};
char* afoo = foo[0];
afoo[2] = 'g'; // access violation

这会产生访问冲突,因为“foo”是在只读内存中分配的(我相信是在.rdata部分)。有没有办法将“foo”强制转移到可写内存中(.data部分)?即使通过#pragma也可以接受!(Visual Studio编译器)

我知道我可以使用strdup和其他一些方法来解决问题,但我想知道是否可以按照我所要求的那样做。 :)


1
不,根据标准修改字符串字面量会引发未定义行为。即使您拥有非const指针,数据本身也是const的。如果这样做,您可能会在运行时遇到问题,或者当您更改甚至修补编译器时遇到问题。 - John Dibling
修改字符串字面值没有意义。也许用整数更清楚:我如何使5 = 7编译? - fredoverflow
Anne,这并不是你应该做的事情,但实际上是可能的,违背了C++标准所说的。我写了一篇文章,解释了如何在公开了内存保护接口给用户空间程序的类UNIX系统中实现这一点,在这里查看 - user405725
参见:c - 为什么用字符串字面量初始化“char *s”时,写操作会导致分段错误,但不会用“char s[]”?- 堆栈溢出 ■ 链接失效,请使用网络档案 https://web.archive.org/web/20130806024514/http://lazarenko.me/2013/05/01/how-constant-is-a-constant ■ 另外注意,尝试以上修改可能会导致字符串冲突/具有相同的地址。 - user202729
6个回答

9

由于这是C++,因此最好的方法是使用字符串类(std::stringQStringCString等,具体取决于您的环境)。

直接回答您的问题,您不应该修改字符串字面值。标准规定这是未定义行为。您确实需要以某种方式复制字符串,否则您会编写不正确的C++代码。


2
具体来说,如果您正在使用具有自己字符串类的框架,则尝试将std::string融入其中只会让您的生活更加困难。因此,“取决于您的环境”。 - Cogwheel

5
我认为最接近的方法是使用字面量初始化一个普通的char[](不是char*[]):
char foo[] = "foo";

但是这种方法在某个时间点仍然会执行复制操作。

唯一的其他解决方法是使用系统级调用来将字符串常量所在的页面标记为可写。此时,您实际上不是在谈论C或C ++,而是在谈论Windows(或您正在运行的任何系统)。大多数系统可能都可以实现(除非数据实际上在ROM中,在嵌入式系统上可能是这种情况),但我确实不知道细节。

哦,别忘了在您的示例中:

char* foo[] = {
    "foo",
    "foo"
};

自C99 6.4.5/6标准(“字符串字面量”)规定以来,以下内容是不确定的:这些数组是否不同,只要它们的元素具有适当的值即可。因此,在该数组中的两个指针是否指向相同或不同的对象,是无法确定的。几乎所有的编译器都会使这两个指针指向相同的地址上的同一个对象,但这并非一定如此,某些更复杂的指向字符串字面量的指针可能会导致编译器生成两个相同的字符串。甚至可能出现一个字符串字面量“存在于”另一个字符串之内的情况:
char* p1 = "some string";
char* p2 = "string";

p2 可能指向 p1 指向的字符串的末尾。

因此,如果您通过某些系统上的黑客攻击开始更改字符串文字,则可能会无意中修改一些“其他”字符串。这是未定义行为可能带来的问题之一。


还有一个 char _foo[] = "foo"; char *foo = &_foo[0] - Dave Butler

2
如果您将字符串存储在数组中,就可以更改它。
没有一种方法可以“正确地”写入只读内存。
当然,您可以停止使用C字符串。

正确是主观的。根据什么来判断正确?从硬件的角度来看,有一种相当正确和直接的方法来做这个,因为在几乎所有常见的硬件中,不存在只读内存这样的东西。 - user405725

1
你可以创建一个多维字符数组:
#include <iostream>

int main(int argc, char** argv)
{
    char foo[][4] = {
        "foo",
        "bar"
    };
    char* afoo = foo[0];
    afoo[2] = 'g';
    std::cout << afoo << std::endl;
}

定义数组的更冗长方式:

char foo[][4] = {
    {'f', 'o', 'o', '\0'},
    {'b', 'a', 'r', '\0'}
};

2
你仍然可以使用字符串字面值,不必将所有字符都指定为字符字面值。 - Edward Strange
2
此外,安妮应该记住,如果她这样做,4必须是最长字符串的大小加1。使用字符串类确实是更好的选择,但这是她所想要的最接近的方法。 - Edward Strange
我将其标记为正确,因为它回答了我所要做的事情(我意识到我不应该尝试这样做,但是,呵呵)。为什么它有效是另一回事... - Anne
1
@John:字面值仅用作初始化char[4]的简短符号表示法。字面值并没有被修改,但栈上的char[2][4]数组被修改是完全有效的。 - Ferdinand Beyer
1
这可能取决于编译器。正如 Ferdinand 指出的那样,GCC 会发出直接将值加载到数组中而不是从字符串字面量复制的代码。这意味着除非您使用相同的字符串字面量初始化多个数组,否则存储字面值的额外空间是不必要的。 - Cogwheel
显示剩余4条评论

1
我不会这样做。因此,我只能提供一个可怕丑陋的黑客方法供您尝试: 获取包含您常量字面值的页面并取消保护该页面。请参阅Win32的VirtualProtect()函数。但是,即使这样做可以起作用,也不能保证始终正确行为。最好不要这样做。

-3

是的。

   (char[]){"foo"}

2
这会引起未定义的行为。 - John Dibling

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