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

Are char arrays guaranteed to be null terminated?

#include <stdio.h>

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

    return 0;
}

Whenever we write a string, enclosed in double quotes, C automatically creates an array of characters for us, containing that string, terminated by the [=16=] character http://www.eskimo.com/~scs/cclass/notes/sx8.html

在上面的示例中,b 仅具有 2 个字符的空间,因此 null 终止字符没有放置位置,但编译器正在重新组织内存存储指令,以便 ac 在内存中存储在 b 之前,以便为数组末尾的 [=15=] 腾出空间。

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

允许使用字符串初始化 char 数组,前提是数组至少大到足以容纳字符串中的所有字符 除了 null终结者。

这在 C standard:

的第 6.7.9p14 节中有详细说明

An array of character type may be initialized by a character string literal or UTF−8 string literal, optionally enclosed in braces. Successive bytes of the string literal (including the terminating null character if there is room or if the array is of unknown size) initialize the elements of the array.

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

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

char b[2] = "hello";

因为这提供了比数组所能容纳的更多的初始化器,并且违反了约束。第 6.7.9p2 节说明如下:

No initializer shall attempt to provide a value for an object not contained within the entity being initialized.

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

char b[] = "hi"; 

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

总结一下:

如果数组大小固定:

  • 如果用于初始化它的字符串常量比数组短,数组将包含字符串中连续元素设置为0的字符,因此数组将包含一个字符串。
  • 如果数组恰好大到足以包含字符串的元素但不是 空终止符,则数组将包含字符串中没有空终止符的字符,这意味着该数组不是字符串。
  • 如果字符串常量(不包括 null 终止符)比数组长,则违反约束条件会触发 undefined behavior

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

你的问题有两点。

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

    char *p = "ABC";  // p references null character terminated string
    
  2. 字符数组可能只能容纳尽可能多的元素,因此如果您尝试使用 三个 元素字符串文字来初始化两个元素数组,只有前两个会被写。所以数组将不包含以空字符结尾的 C string

    char p[2] = "AB";  // p is not a valid C string.
    

Whenever we write a string, enclosed in double quotes, C automatically creates an array of characters for us, containing that string, terminated by the [=38=] character.

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

当你写类似

的东西时
char *p = "Hello";

printf("world!\n");

C 会自动为您创建一个大小合适的字符数组,其中包含以 [=14=] 字符结尾的字符串。

然而,对于数组初始值设定项,情况略有不同。当你写

char b[2] = "hi";

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

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

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

对于b1,你指定了一个大小,但是它太小了,所以编译器应该给你一个错误。

对于您询问的 b2,您指定的大小刚好足以容纳字符串初始值设定项中的显式字符,但 不是 终止 [=14=]。这是一个特例。这是合法的,但是您最终在 b2 中得到的不是正确的以 null 结尾的字符串。由于它充其量是不寻常的,编译器可能会给你一个警告。有关此案例的更多信息,请参阅

对于b3,你指定了一个恰到好处的大小,所以你在一个精确大小的数组中得到了一个合适的字符串,就像b0.

对于 b4,您指定的尺寸过大,但这没有问题。在终止的 [=14=] 之外,数组中最终有额外的 space。 (事实上​​ ,这个额外的 space 也会被 [=14=] 填充。)这个额外的 space 可以让你安全地做一些像 strcat(b4, ", wrld!").

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

还有一件事。您写道:

and yet the compiler is reorganizing the memory store instructions so that a and c are stored before b in memory to make room for a [=14=] at the end of the array.

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

char 数组根本不需要以任何方式终止。它是一个数组。如果实际内容小于数组的维度,那么您需要跟踪该内容的大小。

这里的答案似乎已经退化为字符串讨论。并非所有 char 数组都是字符串。但是,如果要将空终止符作为实际字符串处理,则使用空终止符作为标记是一个非常严格的约定。

您的阵列可能使用其他东西,也可能有分隔符和区域。毕竟它可能是一个联合或覆盖一个结构。可能是另一个系统的临时区域。