char 数组是否保证以 null 终止?

Dan*_*Dan 55 c string-literals array-initialization

#include <stdio.h>

int main() {
    char a = 5;
    char b[2] = "hi"; // No explicit room for `\0`.
    char c = 6;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

每当我们编写一个用双引号括起来的字符串时,C 都会自动为我们创建一个字符数组,其中包含该字符串,并以 \0 字符结尾 http://www.eskimo.com/~scs/cclass/notes/sx8。 html

在上面的示例中,b只有 2 个字符的空间,因此空终止字符没有位置可放置,但编译器正在重新组织内存存储指令,以便将ac存储b在内存中,以便为 a 腾出\0空间数组的末尾。

这是预期的还是我遇到了未定义的行为?

dbu*_*ush 50

char如果数组至少足够大以容纳字符串中空终止符之外的所有字符,则允许使用字符串初始化数组。

\n

C 标准第 6.7.9p14 节对此进行了详细说明:

\n
\n

字符类型数组可以由字符串\n文字或 UTF\xe2\x88\x928 字符串文字初始化,可以选择用大括号括起来。\n字符串文字的连续字节(包括终止 null\n字符,如果有空间或如果该数组的大小未知)\n初始化该数组的元素。

\n
\n

但是,这也意味着您不能将数组视为字符串,因为它不是以 null 结尾的。正如所写,由于您没有执行任何字符串操作b,因此您的代码没有问题。

\n

不能做的是使用太长的字符串进行初始化,即:

\n
char b[2] = "hello";\n
Run Code Online (Sandbox Code Playgroud)\n

因为这给出的初始值设定项超出了数组所能容纳的范围,并且违反了约束条件。第 6.7.9p2 节规定如下:

\n
\n

任何初始化程序都不得尝试为未包含在正在初始化的实体中的对象提供值。

\n
\n

如果您要像这样声明并初始化数组:

\n
char b[] = "hi"; \n
Run Code Online (Sandbox Code Playgroud)\n

那么b将是一个大小为 3 的数组,该数组足够大以容纳字符串常量中的两个字符加上终止空字节,使得b一个字符串。

\n

总结一下:

\n

如果数组有固定大小:

\n
    \n
  • 如果用于初始化它的字符串常量比数组短,则数组将包含字符串中的字符,并且连续元素设置为 0,因此数组将包含一个字符串。
  • \n
  • 如果数组正好足够大以包含字符串的元素但不包含空终止符,则数组将包含字符串中不包含空终止符的字符,这意味着该数组不是字符串。
  • \n
  • 如果字符串常量(不包括空终止符)比数组长,则这是一个约束违规,会触发未定义的行为
  • \n
\n

如果数组没有明确的大小,则数组的大小将调整为容纳字符串常量加上终止空字节。

\n

  • @丹是的。但最好只写 char b[] = "hi";` (11认同)
  • @Dan:有趣的事实:**这在 C++ 中是不同的**。如果使用双引号初始化器,则在 C++ 中使显式大小太小而无法容纳尾随 0 字节是不合法的,因此如果您希望在 C++ 中这样做,则必须编写 `char b[] = {'h', 'i '};` 有时对于 SIMD 查找表来说很烦人,例如 `static char hex_lut[16] = "0123...ef";` 需要第 17 个字节或更少的可读源用于 C++ 中的初始化程序。C 与 C++ 模式下的 GCC 示例,没有警告与错误消息。https://godbolt.org/z/eTx94a4h7 (7认同)
  • @jamesqf 不,那再好不过了。至少让它成为“const”。至于指向“const char”的指针是否比数组更好,这显然取决于用法,因为它无法修改。 (4认同)
  • @Cheatah,这可能不是编译器添加警告,只是显示定义了“b”的代码行,并且注释中存在“没有空间”文本。 (2认同)
  • @RDragonrydr 当使用字符串文字来初始化数组时,其内容(最多可达数组大小)将被复制到数组中。字符串文字本身也可能单独出现在内存中,具体取决于各个编译器以及字符串常量是否在其他地方使用。 (2认同)

Ste*_*mit 35

\n

每当我们编写一个用双引号括起来的字符串时,C 都会自动为我们创建一个字符数组,其中包含该字符串,并以 \\0 字符结尾。

\n
\n

在这种情况下,这些注释具有轻微的误导性。我必须更新它们。

\n

当你写类似的东西时

\n
char *p = "Hello";\n
Run Code Online (Sandbox Code Playgroud)\n

或者

\n
printf("world!\\n");\n
Run Code Online (Sandbox Code Playgroud)\n

C 会自动为您创建一个大小合适的字符数组,其中包含字符串,并以字符结尾\\0

\n

然而,在数组初始值设定项的情况下,情况略有不同。当你写的时候

\n
char b[2] = "hi";\n
Run Code Online (Sandbox Code Playgroud)\n

该字符串只是正在创建的数组的初始值设定项。因此您可以完全控制尺寸。有几种可能:

\n
char b0[] = "hi";     // compiler infers size\nchar b1[1] = "hi";    // error\nchar b2[2] = "hi";    // No terminating 0 in the array. (Illegal in C++, BTW)\nchar b3[3] = "hi";    // explicit size matches string literal\nchar b4[10] = "hi";   // space past end of initializer is always zero-initialized\n
Run Code Online (Sandbox Code Playgroud)\n

对于b0,您没有指定大小,因此编译器使用字符串初始值设定项来选择正确的大小,即 3。

\n

对于b1,您指定了一个大小,但它太小,因此编译器应该给您一个错误。

\n

对于b2,这是您询问的情况,您指定的大小对于字符串初始值设定项中的显式字符来说刚刚足够大,但不是终止的\\0。这是一个特殊情况。这是合法的,但是你最终得到的b2不是一个正确的空终止字符串。由于它充其量是不寻常的,因此编译器可能会向您发出警告。有关此案例的更多信息,请参阅此问题。

\n

对于b3,您指定了一个恰到好处的大小,因此您可以在精确大小的数组中获得正确的字符串,就像 一样b0

\n

对于b4,您指定的大小太大,但这没有问题。数组中最终会出现超出终止符的额外空间\\0。(事实上​​,这个额外的空间也会被填满\\0。)这个额外的空间可以让你安全地做类似的事情strcat(b4,\xc2\xa0",\xc2\xa0wrld!")

\n

不用说,大多数时候您想使用表单b0。计算字符是乏味且容易出错的。正如 Brian Kernighan(C 语言的创建者之一)在这种情况下所写的那样,“让计算机来做那些肮脏的工作。”

\n

还有一件事。你写了:

\n
\n

然而编译器正在重新组织内存存储指令,以便将ac存储b在内存之前,以便为\\0数组末尾的 a 腾出空间。

\n
\n

我不知道那里发生了什么,但可以肯定地说编译器并没有试图“为”腾出空间\\0。编译器可以而且经常按照自己难以理解的内部顺序存储变量,既不匹配您声明它们的顺序,也不匹配字母顺序,也不匹配您可能想到的任何其他顺序。如果在你的编译器数组下面b有额外的空间,其中确实包含一个\\0好像终止字符串的东西,这可能基本上是随机的机会,而不是因为编译器试图对你友善并帮助printf("%s\\n", b)更好地定义之类的东西。(在我尝试过的两个编译器下,printf("%s\\n", b)打印了hi^Ehi ??,清楚地显示了尾随随机垃圾的存在,正如预期的那样。)

\n


P__*_*J__ 6

你的问题有两点。

  1. 字符串字面量。字符串文字(即用双引号括起来的内容)始终是正确的空字符终止字符串。

    char *p = "ABC";  // p references null character terminated string
    
    Run Code Online (Sandbox Code Playgroud)
  2. 字符数组只能容纳尽可能多的元素,因此如果您尝试使用三个元素字符串文字初始化两个元素数组,则只会先写入两个元素。所以数组不会包含以空字符结尾的 C 字符串

    char p[2] = "AB";  // p is not a valid C string.
    
    Run Code Online (Sandbox Code Playgroud)