理解C++中的C-strings和字符串字面值

3

我有几个问题想问关于字符串字面值和C-字符串。

如果我有以下代码:

char cstr[] = "c-string";

据我了解,字符串字面量在内存中创建时带有一个终止空字节。例如,从地址0xA0开始到0xA9结束,在这之后,该地址被返回和/或强制转换为char[]类型,然后指向该地址。
然后,可以合法地执行以下操作:
for (int i = 0; i < (sizeof(array)/sizeof(char)); ++i)
    cstr[i] = 97+i;

因此,在这个意义上,只要将字符串字面量转换为char []类型,它们就可以被修改吗?

但是对于常规指针,我了解到当它们指向内存中的字符串字面量时,它们无法修改内容,因为大多数编译器会将分配的内存标记为“只读”,并放在某些较低的地址空间中用于常量。

char * p = "const cstring";
*p = 'A'; // illegal memory write

我想了解的是为什么char *类型不能像数组一样指向字符串常量并修改它们的常数值? 为什么字符串常量不能转换为char *而可以转换成char []? 如果我对这里的想法有误或完全错了,请随时纠正我。

char * p = "const cstring";应该会抛出编译错误,因为"const cstring"是类型为const char*的常量(特别指您不应该像在示例中使用它一样使用它)。 - tylerl
@tylerl "const cstring" 不是类型为 const char* 的变量。 - Lightness Races in Orbit
从C++11开始,字符串字面量的类型是const char[N],这与const char*几乎等效。你可以争辩一些迂腐的细节,但那只会增加混淆而不是清晰度。@LightnessRacesinOrbit - tylerl
@tylerl 相反,它们是完全不同的类型,假装它们相同只会引入混乱。 - Lightness Races in Orbit
4个回答

5
你所缺失的是编译器的一些巧妙技巧,大致如下:

char cstr[] = "c-string"; 

实际执行的方式如下所示:
char *cstr = alloca(strlen("c-string")+1);
memcpy(cstr,"c-string",strlen("c-string")+1);

你看不到这部分,但它基本上就是代码编译后的样子。


这绝对是我一直缺失的东西!我选择的答案基本上已经用言语表达了这个意思,但代码更加简洁 ;) 所以 cstr 实际上是指向栈上(或可能是堆上)本地分配内存的 const char *,它是一个可修改的副本,不像字面字符串的值。非常感谢你向我展示了这个。 - Bobby Barjasteh
值得指出的是,在这种情况下,cstr 不是 const char*,而是 char*const char* 是使用字符串字面量时得到的。这意味着您不能修改其内容。相反,char* 表示数据是可修改的。此外,它非常明显在堆栈上,而不是堆上,这就是为什么您不必调用 free(),以及为什么您不能将其返回给调用者的原因。 - tylerl

2

char cstr[] = "something"; 声明了一个自动数组,初始化为字节's'、'o'、'm'、...

另一方面,char * cstr = "something"; 声明了一个字符指针,其地址被初始化为文本“something”的地址。


谢谢您对此的见解。我现在明白了,数组作为标准类型,在其构造函数中通过字符串字面量进行初始化,但它们只是字面量本身的副本。 - Bobby Barjasteh

1
char cstr[] = "c-string";

这将"c-string"复制到堆栈上的char数组中。可以在此内存中写入数据是合法的。

char * p = "const cstring";
*p = 'A'; // illegal memory write

像 "c-string" 和 "const cstring" 这样的字面字符串存储在二进制文件的数据段中。该区域是只读的。指针 p 指向该区域中的内存,写入该位置是不合法的。自 C++11 以来,这比以前执行得更加严格,您必须将其声明为 const char* p

相关问题 在这里


1

在第一种情况下,您正在创建一个实际的字符数组,其大小由您使用的字面量的大小确定(8+1个字节)。cstr变量在堆栈上分配内存,并将字符串字面量的内容(在代码中可能位于内存的只读部分)复制到此变量中。

在第二种情况下,本地变量p也在堆栈上分配内存,但其内容将是您用其初始化的字符串字面量的地址。

因此,由于字符串字面量可能位于只读内存中,通常不安全尝试通过p指针更改它(您可以做到,也可能做不到)。另一方面,您可以对cstr数组进行任何操作,因为那是您的本地副本,只是恰好从字面量初始化而已。

(只有一个注意事项:变量cstr字符数组类型,在大多数情况下,这会转换为指向该数组第一个元素的指针。例外情况可能是例如sizeof运算符:它计算整个数组的大小,而不仅仅是指向第一个元素的指针。)


啊,我明白了。所以它们的区别在于一个只是指向 RO 存储器的指针,而另一个是 char 数组的变量(或者换句话说是常量指针),指向内存中该字面值的本地副本加上空字节,因此可以访问该副本的 RW。感谢您的澄清。 - Bobby Barjasteh
顺便说一句,以前并不总是只读内存。在将字符串放入只读段之前,修改“常量”字符串是老式C程序员的技巧。 - Hot Licks

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