为什么可以将字符串指针初始化为字符串字面量,但不能将其初始化为数组?

5

字符串可以使用字符串字面量进行初始化

char word1[] = "abc";

或者作为带有空终止符的字符数组。

char word2[] = {'a', 'b', 'c', '\0'};

除了使用 word1[] 的写法,也可以使用指针符号来表示 word1

char *word1 = "abc";

然而,当试图使用指针符号编写word2时

char *word2 = {'a', 'b', 'c', '\0'};

这段代码向我显示了一些警告,例如

警告:标量初始化器中存在过多元素 char *word2 = {'a', 'b', 'c', '\0'};

并且当我运行程序时,我得到了 Segmentation fault (core dumped) 错误。

为什么会这样?为什么可以使用 char *word = "abc" 但不能使用 char *word = {'a', 'b', 'c', '\0'}?


1
尝试使用复合字面量char *word2 = (char[4]){'a', 'b', 'c', '\0'}; - pmg
@pmg 非常感谢您的指引。复合字面量对我来说仍然有点太新颖了,难以记住。我已经更新了我的答案。 - Steve Summit
3个回答

9
为什么可以将字符串指针初始化为字符串字面值,但不能将其初始化为数组?
因为 '{'a', 'b', 'c', '\0'}' 不是一个数组,它是要放入被初始化对象的一组值。
在C语言中,语法'{ 'a','b','c','\0'}'并不代表一个数组。人们看到它被用于初始化数组时,但在这种情况下,它只是一组值。它还可以用于初始化结构体,因为它只是列出要放入被初始化对象的值。从本质上说,它不是一个数组。
在'char * word2 = {'a','b','c','\0'};'中,使用值'a'、'b'、'c'和'\0'来初始化'word2'没有意义。它只是一个指针,应该用一个值进行初始化。给出四个值来初始化一个东西是没有意义的。
在'char * word2 = "abc";'中,'"abc"'不是值列表,而是一个字符串字面值。字符串字面值定义了一个静态数组,其中填充了字符串的字符。然后,字符串字面值会自动转换为指向其第一个元素的指针,而这个指针就是用来初始化'word2'的。
所以'char * word2 = "abc";'做了两件事:字符串字面值定义了一个数组,并且初始化将'word2'设置为指向该数组的第一个元素。相比之下,在'char * word2 = {'a', 'b', 'c', '\0'};'中,没有任何东西来定义一个数组;值列表只是一组值。
将其与数组初始化进行比较,在'char word2[] = {'a','b','c','\0'};'中,数组使用值列表进行初始化,这是可以的。然而,在'char word1[] = "abc";'中,有特殊情况发生。C 2018年6.7.9 14号指出,我们可以使用字符串字面值来初始化字符类型的数组,并且将使用字符串的字符来初始化数组的元素。

5

这并没有根本上的原因 - 它只是该语言最初定义的方式。

数组初始化的基本语法为

type array[] = {value, value, value};

指针初始化的基本语法为:
type *pointer = value;

但是,我们也有字符串字面量。实际上,编译器对字符串字面量进行的操作在本质上是两个几乎完全不同的方面。

如果你这样说:

char array[] = "string";

编译器会将其处理得几乎与你实际编写的代码一样。
char array[] = { 's', 't', 'r', 'i', 'n', 'g', '\0' };

但是如果您说

char *p = "string";

编译器会做一些完全不同的事情。它会在背后默默地为您创建一个数组,其中包含该字符串,差不多就像您编写了:
char __hidden_unnamed_array[] = "string";
char *p = __hidden_unnamed_array;

然而,回答你的问题的关键是编译器只会对字符串字面值做这种特殊处理。至少在C语言最初的定义中,没有办法使用{value, value, value}语法创建一个隐藏、无名称的数组,并对其进行其他操作。 {value, value, value}语法仅被定义为作为显式声明数组的直接初始化器。

正如@pmg在评论中提到的,新版本的C语言有一种新的语法——"复合字面量",它基本上让你可以使用{value, value, value}语法创建一个隐藏的、无名称的数组并对其进行其他操作。 因此,你实际上可以编写:

char *word2 = (char[]){'a', 'b', 'c', '\0'};

这个非常好用。在其他情况下也可以使用,例如,您可以说类似于:

printf("%s\n", (char[]){'d', 'e', 'f', '\0'});

回到你问的一个副问题:当你写下

char *word2 = {'a', 'b', 'c', '\0'};

编译器对自己说,“等一下,word2是一个东西,但初始化程序有四个。所以我会丢弃三个,并警告程序员我正在这样做。”接着它做了相当于...
char *word2 = {'a'};

如果您稍后尝试类似以下的操作

printf("%s", word2);

printf尝试访问地址0x00000061时,您遇到了崩溃的情况。


3

通常情况下,初始化器的类型必须与被初始化的内容的类型相匹配。

以下是正确的示例:

char *word1 = "abc";

由于字符串常量具有char数组类型,当在表达式或初始化中使用时,这样的数组会衰减为char *类型,因此与声明的类型相匹配。

这个例子是可行的:

char word2[] = {'a', 'b', 'c', '\0'};

由于使用字符的初始化列表(技术上它们具有int类型但被转换为char),因此数组char被初始化。

这会导致警告:

char *word2 = {'a', 'b', 'c', '\0'};

因为正在使用初始化器列表来初始化不是数组或结构的类型。

这样做是可以的:

char word1[] = "abc";

因为C标准中专门允许使用字符串字面值初始化char数组,参见第6.7.9p14节:
“字符类型的数组可以使用字符串字面值或UTF-8字符串字面值进行初始化,可选地用大括号括起来。字符串字面值的连续字节(包括终止的空字符,如果有足够的空间或者数组大小未知)将初始化数组的元素。”

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