为什么字符串在新编译器中不可修改

Why string is not modifiable in new compilers

本文关键字:修改 编译器 字符串 为什么      更新时间:2023-10-16

在Turbo C/C++时代,您可以像在中一样轻松地修改字符串

char * str = "Hello";
str[1] = '1'; //it will make it H1llo;

现在它们存储在.bss中,不允许您直接修改它们。为什么?这难道不会使修改字符串变得困难吗?

有什么快速的治疗方法(但没有副作用)吗?我可以使用类似strdup()的函数,但直接修改字符串确实很有趣。

正如您所观察到的,标准不允许您修改字符串文字的内容,现代编译器的/OS会相应地安排内存权限。

原因是编译器可能会在代码的许多地方看到使用的文字,例如:

in x.cpp:    std::cerr << "Error" << separator << msg << 'n';
in y.cpp:    if (x == "Error") ...
in z.cpp:    q = "StackOverflowError";

避免所有这些字符串文字分别出现在可执行映像和加载的进程内存中是非常可取的;相反,编译器可以安排包含"StackOverFlowError\0"的单个内存区域,并在使用点使用指向相关起始字符(无论是"S"还是"E")的指针。

如果允许您修改该值——可能是决定让x.cpp显示"Alert"而不是"Error",它也可能无意中破坏y.cpp和z.cpp的代码。

有快速解决办法吗?

好吧,如果取决于你认为什么坏了。如果你想用一种方法来修改字符串文字,那么不……由于上面解释的原因,这是未定义的行为,内存保护机制会随着操作系统等的不同而变化。如果你想以类似的方式修改文本数据,那么是的:char* s = "abc";s放在堆栈上,但正如你所观察到的,它指向.bss数据。如果你改为写:

char s[] = "abc";

然后s仍然在堆栈上,但现在是一个可容纳4个字符的数组,字符串文字仍然在.bss中,但无论何时运行该行,它都会从后者复制到前者,之后您可以修改基于堆栈的副本ala s[1] = 'x';

当然,将数据放入std::string通常是更好的方法。

在C标准下,修改字符串文字是未定义的行为

事实上,你可以用旧的编译器来做这件事,这并不意味着它在所有编译器中都是合法的,它们只是过去更宽容,并没有保护你不写这个内存区域。

你可以做:

char str[] = "Hello";
str[1] = '1';

这将创建一个可变的char数组,并使用字符串文字值的副本(包括终止符)对其进行初始化。

回答"为什么"的问题。其他人已经提到的琐碎答案是"这是未定义的行为"。这意味着该标准允许编译器执行编译器想要的任何操作。

但为什么它是未定义的行为?原因是在大型软件项目中,你经常会得到数千个重复的字符串,也许你有一个宏,如果它检测到一些不一致并打印出一条消息,就会导致程序崩溃。此宏在数千个位置重复,每次都打印相同的消息。如果字符串是可修改的,那么消息将不得不在最终的二进制文件中重复数千次。为了防止大量链接器对二进制文件中的字符串执行重复数据消除,这意味着如果程序中有printf("foon"); printf("foon");,则两个"foon"字符串将位于同一内存位置。现在假设您有:char *foo = "foon"; printf("foon");编译器可能认为它可以消除字符串的重复,而在程序的其他地方,您可以执行*foo = 'b';。现在printf将不正确。为了防止这种情况,修改字符串文字是未定义的行为,现代编译器将使用这一事实来消除重复字符串(在比我这里的示例更高级的级别上),并希望以修改字符串文字会崩溃的方式生成程序。