为啥修改指向字符串文字的指针的内容是错误的?

Posted

技术标签:

【中文标题】为啥修改指向字符串文字的指针的内容是错误的?【英文标题】:Why is wrong to modify the contents of a pointer to a string litteral?为什么修改指向字符串文字的指针的内容是错误的? 【发布时间】:2015-09-28 11:15:06 【问题描述】:

如果我写:

char *aPtr = "blue"; //would be better const char *aPtr = "blue"
aPtr[0]='A';

我有一个警告。上面的代码可以工作,但不是标准的,它具有未定义的行为,因为它是只读内存,指针指向字符串字面量。问题是: 为什么会这样? 而是使用此代码:

char a[]="blue";
char *aPtr=a;
aPtr[0]='A';

没问题。我想了解幕后发生的事情

【问题讨论】:

***.com/a/30661089/2912665 无论是作为重复关闭的问题还是@HappyCoder 提到的问题都与这个问题非常相似。它们都处理字符串文字的类型。这是在问 为什么 字符串文字具有那种类型。 不仅仅是“会更好”; “将是合法的”。你知道这已经被问了一百万次了。 ***.com/questions/32807364/… Why do compilers allow string literals not to be const? 的可能副本 【参考方案1】:

第一个是指向由编译器创建并放置在程序的只读部分中的只读值的指针。您不能修改该地址的字符,因为它们是只读的。

第二个创建一个数组并从初始化程序中复制每个元素(有关详细信息,请参阅this answer)。你可以修改数组的内容,因为它是一个简单的变量。

第一个以它的方式工作,因为做任何其他事情都需要动态分配一个新变量,并且需要垃圾收集来释放它。这不是 C 和 C++ 的工作方式。

【讨论】:

好的,如果我理解在第一种情况下编译器将字符串litteral 放在区域常量中,因此修改值并非不可能。在第二种情况下,数组在堆栈上。对吗? 是的。在这两种情况下,您声明的变量都在堆栈上,但变量类型不同。在第一种情况下,该变量只是指针p,它指向 指向存储在别处的常量数组。在第二种情况下,变量是数组a,它不指向任何地方,它是堆栈上的一个数组。您可以修改a[0],因为它是变量的一部分。你不能修改p[0],因为它是一个常量。【参考方案2】:

字符串文字不能被修改(没有未定义行为)的主要原因是支持字符串文字合并。

很久以前,当内存比现在更紧的时候,编译器作者注意到许多程序的相同字符串文字重复了很多次——尤其是模式字符串传递给fopen(例如f = fopen("filename", "r");)和简单格式字符串被传递给printf(例如,printf("%d\n", a);)。

为了节省内存,他们会避免为这些字符串的每个实例分配单独的内存。相反,他们会分配 一个 内存,并将所有指针指向它。

在某些情况下,他们甚至比这更棘手,以合并甚至不完全相同的文字。例如考虑这样的代码:

printf("%s\t%d\n", a);
/* ... */
printf("%d\n", b);

在这种情况下,字符串文字并不完全相同,但第二个与第一个结尾的部分相同。在这种情况下,他们仍然会分配一块内存。一个指针指向内存的开头,另一个指向%d 在同一块内存中的位置。

由于可能(但不要求)字符串文字合并,基本上不可能说出修改字符串文字时会得到什么行为。如果字符串文字被合并,修改一个字符串文字可能会修改其他相同或相同结尾的字符串。如果不合并字符串字面量,修改一个不会影响其他任何一个。

MMU 增加了另一个维度:它们允许将内存标记为只读,因此尝试修改字符串文字会产生某种信号——但前提是系统具有 MMU(这通常是可选的一次)并且还取决于编译器/链接器是否决定将字符串文字放入内存中,它们是否标记为常量。

由于他们无法定义修改字符串文字时的行为,因此他们认为修改字符串文字会产生未定义的行为。

第二种情况完全不同。在这里,您定义了一个 char 数组。很明显,如果您定义两个单独的数组,它们仍然是独立的,无论内容如何,​​因此修改一个不可能影响另一个。行为是明确的,并且一直如此,因此这样做会给出定义的行为。有问题的数组可能是从字符串文字初始化的这一事实并没有改变。

【讨论】:

即使不考虑合并,如果可以修改文字,char* foo() return "foo"; foo()[0] = 'b'; puts(foo()); 应该做什么?要么您允许foo() 的返回值根据任意调用者对其进行的操作而改变,要么您需要在每次调用它时分配一个新字符串,然后以某种方式对其进行垃圾收集。两者都不是一个好的选择。 @JonathanWakely:虽然可以想象/假设(很多)其他原因,但在讨论为什么要完成时,字面合并是根本不同的: 文字合并是在最初的 C 标准化过程中真正讨论过的(相当多的),这导致了最初的 C89/90 标准。

以上是关于为啥修改指向字符串文字的指针的内容是错误的?的主要内容,如果未能解决你的问题,请参考以下文章

为啥新的 Visual Studio 将字符串文字作为指向常量的指针?

C语言,用指针方式定义的字符串为啥不能修改?

char *和const char *之间的区别?

c语言中,为啥不能对字符指针变量指向的字符串再赋值?

当我想修改指向常量整数的指针时,为啥我的编译器不显示错误?

C语言中 内存消亡 指向她的指针就一定消亡或成了空指针为啥是错的啊