2015-09-28 54 views
0

如果我写:为什么修改指向字串的指针的内容是错误的?

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

我有一个警告。上面的代码可以工作,但不是标准的,它有一个未定义的行为,因为它是一个只读存储器,在字符串处有一个指针。问题是: 这是为什么? 与此代码相反:

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

没问题。我想在引擎盖下了解发生了什么

+1

http://stackoverflow.com/a/30661089/2912665 – CinCout

+0

无论是作为重复关闭的问题还是由@HappyCoder提及的问题都非常类似于此问题。它们都处理字符串文字的类型。这是问**为什么**字符串文字有这种类型。 –

+0

不只是“会更好”; “将是合法的”。你知道这已被问了一百万次。 –

回答

1

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

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

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

+0

好吧,如果我在第一种情况下理解编译器把字符串litteral在区域常量中,因此修改该值不是不可能的。在第二种情况下,而不是数组在堆栈上。这是正确的? – Nick

+0

是的。在这两种情况下,您声明的变量都在堆栈中,但是不同类型的变量。在第一种情况下,变量只是指针“p”,它指向一个常量数组存储在别处。在第二种情况下,变量是数组'a',它并不指向任何地方,它是堆栈中的数组。你可以修改'a [0]',因为它是变量的一部分。你不能修改'p [0]'因为它是一个常量。 –

+0

谢谢,现在一切都清楚了 – Nick

1

字符串文字不能被修改(没有未定义的行为)的主要原因是支持字符串文字合并。编译器作者注意到很多程序都有相同的字符串文字重复多次 - 尤其是像模式字符串传递到fopen(例如,f = fopen("filename", "r");)和简单格式字符串传递到printf(例如,printf("%d\n", a);)。

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

在少数情况下,它们甚至比这更复杂,以合并甚至完全相同的文字。例如,考虑这样的代码:

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

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

有可能(但没有要求)字符串文字合并,当修改字符串文字时,说出你会得到什么样的行为是不可能的。如果合并字符串文字,修改一个字符串文字可能会修改其他相同或相同的结尾。如果字符串文字不合并,修改一个将不会影响其他任何文字。

MMU增加了另一个维度:它们允许内存被标记为只读,所以尝试修改字符串文字会导致某种信号 - 但只有当系统有MMU时(通常是可选的一次),也取决于编译器/链接器是否决定将字符串文字放在内存中,它们是否标记为常量。

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

第二种情况完全不同。在这里你定义了一个数组char。很明显,如果你定义了两个单独的数组,它们仍然是独立的,不管内容如何,​​所以修改一个不会影响另一个。行为是清楚的,而且一直都是这样,所以这样做给定了行为。有问题的数组可能是从字符串文字初始化的事实不会改变这一点。

+0

即使没有考虑合并,如果文字可以被修改什么'char * foo(){return“foo”; } foo()[0] ='b'; puts(foo());'做?您可以根据任意调用者对它的处理方式来改变'foo()'的返回值,或者每次调用它时都需要分配一个新字符串,然后以某种方式垃圾回收。两者都不是一个好的选择。 –

+0

@JonathanWakely:虽然当然可以想象/假设(其他一些)其他原因,但在谈论为什么要这样做时,文字合并有着根本的不同:​​文字合并是一个真正讨论过的问题(相当重要)在原来的C标准化过程中产生了原始的C89/90标准。 –