C和C++之间的字符串字面值差异

20
据我所知,在C++11之前,对于字符串字面值的处理方式在C和C++之间几乎完全相同。
现在,我承认在宽字符串字面值的处理方面,C和C++之间存在差异。
我所能找到的唯一区别在于通过字符串字面值初始化数组的方式。
char str[3] = "abc"; /* OK in C but not in C++ */
char str[4] = "abc"; /* OK in C and in C++. Terminating zero at str[3] */

在C++中有一个仅在技术上的区别。在C++中,"abc"const char [4],而在C中是char [4]。但是,C++有一个特殊规则,允许将其转换为const char *,然后再转换为char *,以保持与C兼容性,直到C++11不再应用该特殊规则。

还有一个字面量长度允许的差异。然而,实际上,任何同时编译C和C++代码的编译器都不会强制执行较低的C限制。

我有一些相关的有趣链接:

还有其他的差异吗?


2
在我们之前的讨论中,const char[N]char[N]的区别非常大。C++禁止修改字符串字面量的规则也禁止修改const对象。你不会发现任何C++特殊情况,就像C规则明确禁止写入存储字符串字面量的内存那样。 - Ben Voigt
2
代码 char str[4] = "abc"; 不是赋值,而是初始化。 - Yu Hao
1
奇怪的是,C++确实有相同的规则。2.14.5p12。但它与“const”类型重复了。 - Ben Voigt
5
当你写下"char x[4] = "abc";"时,实际上是将一个(只读区)const数组复制到一个(基于栈的)非const数组中。语义上等同于"memcpy(x, "abc", 4);"。希望编译器在编译时检查大小并防止溢出操作。 - xryl669
4
这更像是一个有关初始化char数组的问题,而不是字符串字面值的问题。 - tenfour
显示剩余15条评论
2个回答

10

原始字符串

一个显著的区别是C++中的字符串字面值是C的超集。具体而言,C++现在支持原始字符串(C中不支持),在§2.14.15中有技术定义,并且通常用于HTML和XML中,其中经常遇到"

原始字符串允许您以以下形式指定自己的分隔符(最多16个字符):

R"delimiter(char sequence)delimiter"

这对于避免不必要的转义字符非常有用,可以提供您自己的字符串分隔符。以下两个示例展示了如何避免分别转义 "(
std::cout << R"(a"b"c")";      // empty delimiter
std::cout << '\n';
std::cout << R"aa(a("b"))aa";  // aa delimiter
// a"b"c"
// a("b")

演示


charconst char

另一个区别是,如评论中所指出的,在C语言中,字符串字面量的类型为char [n],在§6.4.5/6中有规定:

对于字符串字面量,数组元素的类型为char,并用多字节字符序列的各个字节进行初始化。

而在C++中,它们的类型为const char [n],在§2.14.5/8中定义:

普通字符串字面量和UTF-8字符串字面量也被称为窄字符串字面量。窄字符串字面量的类型为“长度为n的const char数组”,其中n是下面定义的字符串大小,并具有静态存储期(3.7)。

这并不改变事实,在两种标准中(分别在C和C++的§6.4.5/7和2.14.5/13处),尝试修改字符串字面量都会导致未定义行为。


未指定 vs 实现定义 (ref)

另一个微妙的区别在于,在C语言中,字符串字面量的字符数组是否不同是未指定的,根据§6.4.5/7:

只要它们的元素具有适当的值,这些数组是否不同是未指定的。

而在C++中,这是实现定义的,根据§2.14.5/13:

所有字符串字面量是否不同(即存储在不重叠对象中)是实现定义的。


你能详细说明一下“未指定”和“实现定义”的实际区别吗?肯定有一些区别,否则就不会有两种不同的措辞了... - rodrigo
1
@rodrigo,如果我理解措辞正确的话,技术上的区别在于当undefined时,实现可以选择任何可能性,并且不需要记录它;而当它是implementationdefined时,它需要提供关于所做选择的文档。无论如何,在第三个标题旁边,有一个参考链接指向这里SO上一个有关该差异的问题。 :) - Shoe

-1

回答你的问题最好的方法是将其重写为一个程序,当使用“C”或“C ++”编译器时编译结果相同。我假设你正在使用GCC,但其他(正确编写的)编译器工具链应该提供类似的结果。

首先,我将解决你提出的每个问题,然后给出一个程序来提供答案(和证明)。

  • 据我所知,在C++11之前,字符串字面值在C和C ++之间的处理方式几乎完全相同。

它们仍然可以使用各种命令行参数以相同的方式处理,在此示例中,我将使用“-fpermissive”(作弊)。您最好找出为什么会收到警告并编写新代码以避免任何警告;只使用CLP“作弊”来编译旧代码。

编写新代码时要正确无误(没有作弊和没有警告,不用说没有错误)。

  • 现在,我承认在处理宽字符串字面值方面,C和C ++之间存在差异。

在不同的情况下,你可以通过作弊来消除大部分或全部差异,因此它们之间并没有太大区别。作弊是错误的,要学会正确编程并遵循现代标准,而不是过去的错误或笨拙方式。有些事情是按照一定方式完成的,这样对你和编译器都有帮助(记住,你不是唯一一个“看到”你的代码的人)。

这种情况下,编译器需要足够的空间来用'0'(零字节)终止字符串。这允许使用打印(和其他一些)函数而无需指定字符串的长度。

如果你只是想编译从某个地方获取的现有程序,并且不想重新编写它,只是想编译并运行它,那么使用作弊(如果必须)来跳过警告并将编译强制转换为可执行文件。

  • 你所写的其余部分...

不行。

.

请看这个示例程序。我稍微修改了你的问题,将其变成了一个程序。使用“C”或“C ++”编译器编译此程序的结果是相同的。

将下面的示例程序文本复制并粘贴到名为“test.c”的文件中,然后按照开头的说明操作。只需使用“cat”命令打开文件即可回滚(并查看)它,而无需打开文本编辑器,然后复制并粘贴每行以编译器命令开头的行(接下来的三行)。

请注意,正如评论中指出的那样,使用现代g ++编译器运行此行“g ++ -S -o test_c ++.s test.c”会产生错误,因为容器不足以容纳字符串。

您应该能够阅读此程序,而实际上不需要编译它即可查看答案,但如果您希望这样做,它将编译并生成输出供您检查。

正如您所看到的,当字符串以空字符结尾时,变量“str1”的长度不足以容纳该字符串,在现代(正确编写的)g ++编译器上会产生错误。


/* Answer for: https://dev59.com/p2Ag5IYBdhLWcg3w0Nve
 *
 * cat test.c
 * gcc -S -o test_c.s test.c
 * g++ -S -o test_c++.s test.c
 * g++ -S -fpermissive -o test_c++.s test.c
 *
 */

char str1[3] = "1ab";
char str2[4] = "2ab";
char str3[]  = "3ab";

main(){return 0;}


/* Comment: Executing "g++ -S -o test_c++.s test.c" produces this Error:
 *
 * test.c:10:16: error: initializer-string for array of chars is too long [-fpermissive]
 * char str1[3] = "1ab";
 *                ^
 *
 */


/* Resulting Assembly Language Output */

/*      .file   "test.c"
 *      .globl  _str1
 *      .data
 * _str1:
 *      .ascii "1ab"
 *      .globl  _str2
 * _str2:
 *      .ascii "2ab\0"
 *      .globl  _str3
 * _str3:
 *      .ascii "3ab\0"
 *      .def    ___main;    .scl    2;  .type   32; .endef
 *      .text
 *      .globl  _main
 *      .def    _main;  .scl    2;  .type   32; .endef
 * _main:
 * LFB0:
 *      .cfi_startproc
 *      pushl   %ebp
 *      .cfi_def_cfa_offset 8
 *      .cfi_offset 5, -8
 *      movl    %esp, %ebp
 *      .cfi_def_cfa_register 5
 *      andl    $-16, %esp
 *      call    ___main
 *      movl    $0, %eax
 *      leave
 *      .cfi_restore 5
 *      .cfi_def_cfa 4, 4
 *      ret
 *      .cfi_endproc
 * LFE0:
 *      .ident  "GCC: (GNU) 4.8.2"
 *
 */

4
好的解释。但遗憾的是,你漏掉了主题。 - dhein
我不同意,我确实准确回答了这个问题。 - Rob
2
OP 要求提供与他所述的其他差异。你只是解释他已经知道的东西,正如他所说的那样。如果你问我,这就是缺少主题。 - dhein
OP将他的问题范围限制在“字符串文字”上,并没有扩展他的问题到字符串的每种可能用法(例如:导致字符串被处理不同或需要字面值与我描述的不同的新功能或旧功能更改)。所以我确实说了“不”,也许对你来说不够“字面”,除了我描述的内容之外。感谢您抽出时间解释为什么您不同意我的答案。我曾经提供过更长的答案,但发现它们被编辑成简洁,因此我从那时起尝试避免冗余。 - Rob

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