c-字符数组是否保证以null终止


#include <stdio.h>
int main() {
char a = 5;
char b[2] = "hi"; // No explicit room for ``.
char c = 6;
return 0;
}

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

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

这是意料之中的,还是我遇到了未定义的行为?

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

这在C标准的6.7.9p14节中有详细说明:

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

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

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

char b[2] = "hello";

因为这提供了比数组中所能容纳的更多的初始值设定项,并且违反了约束。第6.7.9p2节规定如下:

任何初始值设定项都不应尝试为实体中不包含的对象提供值正在初始化。

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

char b[] = "hi"; 

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

总结:

如果阵列具有固定大小:

  • 如果用于初始化它的字符串常量比数组短,则数组将包含字符串中的字符,并且连续元素设置为0,因此数组将包含一个字符串
  • 如果数组刚好足够大,可以包含字符串的元素,但没有null终止符,则数组将包含字符串中没有null终止符的字符,这意味着数组不是字符串
  • 如果字符串常量(不计算null终止符)比数组长,则这是触发未定义行为的约束冲突

如果数组没有显式大小,则数组的大小将保持字符串常量加上终止的空字节。

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

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

当你写类似的东西时

char *p = "Hello";

printf("world!n");

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

然而,在数组初始化程序的情况下,情况略有不同。当你写

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,也就是您询问的情况,您指定的大小刚好不足以容纳字符串初始值设定项中的显式字符,但而不是终止的。这是一个特殊情况。这是合法的,但在b2中最终得到的不是一个正确的以null结尾的字符串。由于这充其量是不寻常的,编译器可能会给你一个警告。有关此案例的详细信息,请参阅此问题。

对于b3,您可以指定一个刚好合适的大小,这样您就可以在一个刚好大小的数组中获得一个合适的字符串,就像b0一样。

对于b4,您可以指定一个太大的大小,尽管这没有问题。在终止的之外,阵列中最终会有额外的空间。(事实上,这个额外的空间也会被填满。)这个额外的空格可以让你安全地做strcat(b4, ", wrld!")之类的事情。

不用说,大多数时候你都想使用b0表单。计算字符既繁琐又容易出错。正如Brian Kernighan(C的创建者之一)在这种背景下所写的那样;让电脑来干脏活吧">

还有一件事。您写道:

,但编译器正在重新组织内存存储指令,以便在内存中的b之前存储ac,为数组末尾的腾出空间。

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

您的问题有两件事。

  1. 字符串文字。字符串文字(即用双引号括起来的东西)总是正确的以空字符结尾的字符串。

    char *p = "ABC";  // p references null character terminated string
    
  2. 字符数组可能只包含与现有元素一样多的元素,因此,如果您尝试用三个元素的字符串文字初始化两个元素数组,则只会先写入两个。因此数组将不包含以空字符结尾的C字符串

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

一个char数组根本不需要被任何东西终止。它是一个数组。如果实际内容小于数组的大小,则需要跟踪该内容的大小。

这里的答案似乎已经退化为一连串的讨论。并非所有的char数组都是字符串。然而,如果要将null终止符作为事实上的字符串处理,则使用null终止符来作为sentinel是一种非常强的约定。

您的数组可能使用其他内容,也可能具有分隔符和区域。毕竟,它可能是一个联盟或覆盖一个结构。可能是另一个系统的集结区。

最新更新