声明一个没有const的C风格字符串是不好的吗?如果是,为什么?

67

用C++实现这个

char* cool = "cool";

编译没有问题,但是会给我一个警告:

从字符串常量转换为 char* 是不赞成的。

我从不会有意地使用 C 风格的字符串而不是 std::string,但以防万一我被问到这个问题:

声明一个没有带有 const 修饰符的 C 风格字符串,这是坏习惯吗?如果是,为什么?


5
顺便说一下,这是C语言而不是C++。 - NinjaDeveloper
2
当编译后,大多数现代架构中的字符串通常位于只读内存段中,就像许多其他常量一样。 - mszymborski
6
请选择一种语言。每种语言的答案都不同。 - NathanOliver
29
这个问题被正确地标记为C和C++,因为它涉及到C和C++之间的一个“差异”。 - zwol
6
在我看来,它已经被暗示得非常明显了,不需要过多阐述 -- 或者说,换句话说,如果不指出C和C++的区别而回答这个问题,我会认为这是不合适的。 - zwol
显示剩余6条评论
8个回答

62

是的,这种写法是不好的实践,因为它允许通过写入字符串字面值来无意中引发多种未定义行为,包括:

cool[0] = 'k';
strcpy(cool, "oops");

另一方面,这是完全可以的,因为它分配了一个非 const 的字符数组:

char cool[] = "cool";

3
事实上,后一种情况(ok)是大多数人所指的“C风格字符串”的含义,这显然是有歧义的。 - Chris Dodd
15
@ChrisDodd 我对此并不确定。我所见过的关于"C-style string"唯一的意思就是"以零结尾的字符数组",这同样适用于char *cool = "cool";const char *cool = "cool";char cool[] = "cool";const char cool[] = "cool";,但不适用于char cool = "cool\0";,因为其中六个字符中只有前五个形成了C风格字符串。即使在Google的帮助下,我也无法找到其他定义。 - user743382
2
char cool[] = "cool";char* cool = "cool"; 有什么不同?我本以为它们基本上是相同的。 - Dave Cousineau
6
前者创建一个数组并使用字符串字面量初始化其元素。后者创建了一个指针,并将它设置为指向字符串字面量中的第一个字符。因此,在前一种情况下编写是安全的,但在后一种情况下不安全。 - plugwash
4
@Liam,实际原因是当你使用 char* cool = "cool" 时,你只是取了 "cool" 文本的地址,而这个文本可能已经被分配到只读内存区域中,写入该区域可能会导致分段错误(segmentation fault)或未定义的行为(suppose the compiler was clever enough to use same literal for all occurences of "cool" in your code)。当你使用 char[] cool = "cool" 时,文本中的字符会被存储在可读写的内存区域中,所以写入是安全的。 - poe123
显示剩余3条评论

17

是的,在C++中,您应该始终使用const char *const char [N]类型的变量引用字符串字面值。这也是编写新的C代码时的最佳实践。

字符串字面值存储在只读内存中,当可能时;它们的类型被正确地加上了const限定符。C语言(但不是C++)包括了一个向后兼容的瑕疵,其中编译器将它们的类型设置为char [N],即使它们存储在只读内存中也是如此。这是因为字符串字面值比const限定符更古老。 const是在现在被称为“C89”的版本中开发出来的,而最初的“K&R”语言形式中没有它。

一些C编译器包括一个可选模式,在该模式下禁用了向后兼容的瑕疵,char *foo = "...";将会得到与C++类似的诊断结果。 GCC将此模式命名为-Wwrite-strings。 我强烈建议对于新代码使用它;但是,为旧代码启用它可能需要大量的无用功。


1
在C语言中,我印象中使用-Wwrite-strings选项会使你在调用带有char *参数的库函数时去掉const修饰符。在这种情况下,我更喜欢不使用该选项。 - yellowantphil
1
@yellowantphil 是的,但因为你从C++调用函数时必须这样做,所以大多数带有C接口的库现在已被修复,因此您不需要转换任何内容。(也就是说,除非实际要修改参数,否则参数被声明为const char *)。 - zwol
字符串字面量存储在只读内存中,但符合规范的编译器并不要求使用只读内存。对这些内存进行写操作是未定义行为 - 它可能会正常工作,也可能会悄无声息地失败,甚至可能会停止程序等等。 - chux - Reinstate Monica
@yellowantphil 这个问题的解释可以在official specification for execvp中找到(查找表格),但你可能还需要阅读http://c-faq.com/ansi/constmismatch.html,以了解他们所说的“C语言限制”究竟有多糟糕。 - zwol
显示剩余2条评论

14

很糟糕,非常糟糕。在C++11中这已经不可能再做了。

修改字符串字面量的内存是未定义的行为。


是的,const字符串是被锁定的,我遇到过很多次,const的行为几乎类似于#define。 - Abr001am

13

首先,char* cool = "cool";不是标准的C++语法。字符串字面值的类型为const char[n]。因此上述代码违反了const正确性,不应该编译。一些编译器,例如GCC允许这样写但会发出警告,因为这是C遗留下来的问题。MSVC将发出错误提示,因为它是错误的。

其次,何不让编译器帮你完成工作呢?如果对其进行标记为const,那么如果您意外地尝试修改它,将会得到一个很好的编译器错误。如果没有这样做,则可能会发生非常严重的运行时错误,这可能更难以找到。


没有人提到,即使可以将“const”强制转换掉,仍然可能导致未定义的行为。这当然是程序员必须明确执行的操作,因此我们可以归咎于这样的程序员造成的所有修改字符串文字引起的问题。 - Iharob Al Asimi
根据这里的说法,字符串字面量的类型不是const - Eugene Sh.
@EugeneSh。已修复。我只看到了C++标签,但它不是标准的C++。 - NathanOliver
@EugeneSh:那是C语言。 - Lightness Races in Orbit

7

这是不好的,因为字符串常量可能只在二进制文件中出现一次(关键字:stringtable,.strtab)。例如,在

char *cool = "cool";
char *nothot = "cool";

两个变量可能指向同一内存位置。修改其中一个变量的内容可能会改变另一个变量的值,因此在此之后

strcpy(nothot, "warm");

你的变成了“温暖”。

简而言之,这是未定义行为。


6

这是一个字符串字面量,因此应该将其作为常量,因为内存可能位于只读部分。如果您有char cool[] = "cool";,那么就没有问题了,内存属于您。


2

char* cool = "cool"

字符串“cool”将被存储在只读块(通常在数据段中),该块在函数间共享。如果您尝试通过指针cool修改字符串“cool”,则在程序运行时会出现诸如段错误之类的错误。如果您使用const char* cool = "cool",则在编译时尝试修改字符串时会出现错误。
您可以阅读此页面以获取更多信息:http://www.geeksforgeeks.org/storage-for-strings-in-c/


0

对于字符串(尤其是使用字符串字面量时),写const是一个好习惯,但在 C 中几乎没有什么差别。在C++中会产生警告,但在C中不会。此外,请记住,一些编译器将 .c 扩展名简单地视为C,而 .C 则为 C++,因此在这些点上要小心。否则,使用 const 来处理字符串的大小写是一个好习惯,这样您不会错误地更改字符串或尝试更改存储在只读内存中的字符串字面量。


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