在C语言的字符串字面量中被禁止使用的内容

3
在K&R书的第104页,我遇到了以下陈述:
char amessage[] = "now is the time"; //an array
char *pmessage = "now is the time";  //a pointer

Individual characters within the array may be changed but amessage will always refer to the same storage. The pmessage pointer may subsequently be modified to point elsewhere, but the result is undefined if you try to modify the string contents...

那么,这是两种情况下他们所指的错误吗?
对于数组,
amessage[] = "allocate to another address"; //wrong?

关于指针,

pmessage[0] = 'n'; //wrong?

我只想知道什么情况下违反这些规则。
谢谢。

好的,指针不是数组,所以 pmessage[0] 肯定行不通。至于另一个问题:也许可以,但为什么不测试一下呢? - 11684
1
@11684 问题不在指针上,char *p = amessage; p[0] = 'h'; 是可以工作的。问题在于 pmessage 指向一个字符串常量。 - ouah
1
我认为任何C编译器都可以告诉你amessage[] = "allocate to another address";存在一个问题:它在语法上不正确。 - Pascal Cuoq
5个回答

6
/* OK, modifying an array initialized by the 
 * elements of a string literal */
amessage[0] = 'n';

/* not OK, modifying a string literal.
 * String literals are non-modifiable */
pmessage[0] = 'n';

请注意,在C语言中,您无法直接赋值数组。如果想要复制一个数组,请使用memcpy函数,或者使用strcpy函数来复制一个字符串。


好的回答。您还可以考虑展示另一个有效的序列:pmessage = amessage;,然后是 pmessage[0] = 'n';。这将有助于澄清主要问题在于修改最初指向的位置 pmessage(即字符串字面量)。 - ArjunShankar
@ArjunShankar 很有趣,我刚在原帖的问题中发表了类似的评论。 - ouah
我没有注意到。是的,这是同样的事情。如果答案本身也包含它会很好。不管怎样,你已经得到了我的一加一。 - ArjunShankar

3
char amessage[] = "now is the time";
/*   ^            ^
     (an array)   (a string literal) */

当您使用字符串字面值来初始化数组时,您正在将数组元素的初始值设置为字面值本身的值。当然,该数组被分配了自己独立的内存。并且您可以修改其内容。

char *pmessage  = "now is the time";
/*   ^            ^
     (a pointer)  (a string literal) */

当您使用字符串字面量初始化指针时,您使指针指向字符串字面量。该字符串字面量可能存储在只读内存中。因此不能修改。但是,指针本身可以被修改。

什么是有效的,什么是无效的?

amessage[0] = 'n'; /* Valid. Modifying array contents.  */
amessage = pmessage; /* Invalid. You cannot assign to an array.  */
pmessage[0] = 'n'; /* Invalid. You're trying to modify a string literal.  */

但是:
pmessage = amessage; /* Valid. You're modifying a pointer. */

随后:
pmessage[0] = 'n'; /* Valid. You just modified pmessage above,
                   and it now points to modifiable memory.  */

最后: 这里有一个关于此问题的C-FAQ条目。读一下是值得的。

灵感来自ouah的答案。我不想对它做大的修改。


@JoshPetitt - 我想这是我第二次在SO上分享相同的C-FAQ条目。该FAQ几乎和K&R书一样神圣;) - ArjunShankar

3

使用指针作为数组并没有本质上的问题,除非这些指针指向常量数据(例如字符串文字)。虽然从语义上讲是不正确的,在没有内存保护的旧时代,pmessage[0] = 'n'; 实际上可以工作,但结果是不可预测的(例如影响程序中所有相同文字的出现)。在现代操作系统中,由于内存保护的存在,这种情况不会发生。字符串文字和其他常量被放置在所谓的只读节(read-only sections)中,当可执行文件被加载到内存中以创建进程时,包含只读节的内存页面被设置为只读,即任何试图更改它们的内容都会导致分段错误。

char amessage[] = "now is the time";

实际上,这只是以下代码的语法糖:

char amessage[] = { 'n','o','w',' ','i','s',' ','t',
                    'h','e',' ','t','i','m','e','\0' };
即它创建一个包含 16 个字符的数组,并用字符串 "now is the time"(连同 NULL 终止符)初始化其内容。 另一方面
char *pmessage = "now is the time";

将相同的字符串数据放置在只读数据中,并将其地址分配给指针pmessage。它的工作方式类似于这样:

// This one is in the global scope so the array is not on the stack
const char _some_unique_name[] = "now is the time";

char *pmessage = _some_unique_name;

_some_unique_name被选择是为了避免与程序中的任何其他标识符发生冲突。通常使用C语言不允许但汇编和链接器可以使用的符号(例如,像string.1634中的点)。

您可以更改指针的值-这将使其指向其他内容,例如指向另一个字符串。但是,您无法更改数组名称后面的地址,即amessage始终将引用为其最初分配的相同数组存储。

您可以使用amessage[i]pmessage[i]引用每个字符串的单个元素,但只能将元素分配给amessage中位于可读写内存中的元素。


pmessage[0] = 'n';”本身没有什么问题,但我对此感到不舒服,也对您随后说的话感到不安。这给人一种修改字符串常量是可以的,唯一阻止这一点的是操作系统内存保护的感觉。 C99第6.4.5节非常清楚:“如果程序尝试修改这样的数组,则行为未定义”。您可能需要考虑重新措辞。 - ArjunShankar
仔细想了想,我也感到不舒服。重新表达一下我的意思,以更好地表达它。 - Hristo Iliev

1
char amessage[] = "now is the time"; //an array

数组名称是一个常量。数组的地址不能被改变,但是数组的内容可以被改变。

因此

amessage[0]='n';//valid and it change the first element

但是

amessage="hello";//if you try then it wrong as array address can not be changed

现在看指针部分:
char *pmessage = "now is the time";  //a pointer

由于它是一个指针,因此它可以指向任何地址。但是内存中的该地址位置可能是可修改的,也可能是不可修改的。即可能只读或者允许读写。

因此,当您尝试使用指针pmessage更改内存中的某些数据时,取决于指向内存的结果。在这里,它指向代码段,因此只读,因此不允许修改。

所以

pmessage[0]='n';//definitely give you error

1
如果您这样做:
char amessage[] = "now is the time"; //an array
char *pmessage = "now is the time";  //a pointer

你可能真的想要做这件事:

const char *pmessage = "now is the time";  //a pointer

当您的程序被编译时,内存中的某个位置将包含字节“现在是时候”(请注意有一个NULL终止符)。这将位于常量内存中。您不应尝试更改它,否则可能会导致奇怪的事情发生(具体会发生什么取决于您的环境以及它是否存储在只读或读写内存中)。因此,虽然K&R试图启发您如何做事情,但实际的方法是将指向常量字符串的指针设置为const,然后编译器会在您尝试更改内容时发出警告。


这个答案提到了 const,因此得到了一个加分。帮助编译器捕捉错误总是很好的。 - ArjunShankar

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