在c中数组初始化的背景会发生啥?

Posted

技术标签:

【中文标题】在c中数组初始化的背景会发生啥?【英文标题】:What happens in the background of array initialization in c?在c中数组初始化的背景会发生什么? 【发布时间】:2017-12-09 00:14:00 【问题描述】:

鉴于这是合法的

uint8_t bytes[4] =  1, 2, 3, 4 ;

这不是:

uint8_t bytes2[4];
bytes2 =  1, 2, 3, 4 ;

1, 2, 3, 4 代表什么?

我假设它既不是右值也不是左值。一个可以扩展成某种东西的预处理器代码糖果?

【问题讨论】:

不涉及预处理器。它只是 initialization 语法。不是分配。 结构也一样... 一个简单的语法 【参考方案1】:

1,2,3,4 是一个initializer-list,一个特定的语法标记,只能在声明数组的那一行使用。

这完全由 C 标准语法规定。它背后没有特别的理由,这就是语言的定义方式。在 C 语法中,数组不能被赋值,也不能被赋值复制。

但是,您可以通过多种方式避开语法限制,一次覆盖所有值。最简单的方法就是创建一个临时数组和memcpy:

uint8_t tmp[] = 5,6,7,8;
memcpy(bytes, tmp, sizeof bytes);

或者,使用复合文字:

memcpy(bytes, (uint8_t[])5,6,7,8, sizeof bytes);

如果对特定应用有意义,您还可以将数组包装在结构中以绕过语法限制:

typedef struct

  uint8_t data [4];
 array_t;

...

array_t bytes =  .data = 1,2,3,4 ;
array_t tmp   =  .data = 5,6,7,8 ;
bytes = tmp; // works just fine, structs can be copied this way

【讨论】:

UV'd。您可能想补充一点,array_t bytes = .data = 1,2,3,4 ;array_t bytes = 1,2,3,4;array_t bytes = 1,2,3,4; 都是等价的。 @chqrlie 当然......但第一个是最明确和自我记录的。第二个是老式 C90,第三个是有问题的样式(尽管它会起作用)。许多编译器对第 3 版发出警告。 您的评论完全正确,出于您提到的原因,我不会使用第三个,但第二个具有可移植到不支持 C99 语法的环境的优势,包括许多 C++ 编译器. 你确定“token”是正确的词吗?通常,token 是词法分析中不可分割的原子,而 initializer-list 是由类似于表达式的 token 构建的。 请不要将其他答案的一部分复制到我的,尤其是在没有给出归属的情况下 - 使它看起来好像文本被盗了。发布几个相互补充的答案是完全可以的。只需对所有有用的答案进行投票,并将最有用的答案标记为已接受。【参考方案2】:

1,2,3,4; 这样的语法称为大括号括起来的初始化列表,它是一个初始化器。只能用于初始化(对于数组类型)。

引用C11,第 6.7.9 章

P11

标量的初始值设定项应为单个表达式,

[数组不是标量类型,因此不适用于我们]

P14

字符类型的数组可以由字符串字面量或 UTF-8 字符串初始化 字面量,可选用大括号括起来。

[我们这里没有使用字符串文字,所以也不适用于我们]

P16

否则,具有聚合或联合类型的对象的初始化程序应使用大括号括起来 元素或命名成员的初始值设定项列表。

[这是我们感兴趣的情况]

还有,P17,

每个大括号括起来的初始化列表都有一个关联的当前对象。没有时 指定存在,当前对象的子对象按顺序初始化 到当前对象的类型:下标递增顺序的数组元素,结构 声明顺序的成员,以及工会的第一个命名成员。[....]

因此,在这里,括号括起来的列表中的值不是直接“分配”给数组,它们用于初始化数组的各个成员。

OTOH,一种数组类型,不是可修改的左值,所以不能赋值。换句话说,数组变量不能作为赋值运算符的LHS。

详细说明,来自C11,第 6.5.16 章

赋值运算符应该有一个可修改的左值作为其左操作数。

【讨论】:

到目前为止,我一直将initialization 视为declaration + assignment,这似乎不适合上述示例。你对初始化的定义是什么? @TheMeaningfulEngineer 注意,它们有不同的约束。 @TheMeaningfulEngineer 我已经更新了相关参考资料,看看是否有帮助。 我不确定你是不是想说这个......但以防万一:在 C 中,任何东西都可以通过括号括起来的初始化列表来初始化。 int x = 42 ; 在 C 中是完全合法的。在这种情况下,42initializer。它是一个表达式,正如需要的那样。【参考方案3】:

1,2,3,4 是一个初始化列表。它可用于指定至少包含 4 个元素的对象的初始值,这些元素可以是数组元素,也可以是包括嵌套对象在内的结构成员。

您不能使用这种语法将值分配给数组,如下所示:

bytes2 = 1,2,3,4;

因为不支持语法,而且数组不是左值。

您可以使用初始化器列表作为称为复合文字的 C99 语法的一部分来创建对象并将它们用作赋值、返回值或函数参数的右值:

struct quad  int x, y, z, t; ;

struct quad p;
p = (struct quad)1,2,3,4;

你仍然不能将它用于数组,因为它们不是左值,但你可以通过调用 memcpy() 来实现相同的效果:

uint8_t bytes2[4];
memcpy(bytes2, (uint8_t[4])1,2,3,4, sizeof(bytes2));

这条语句由clang 编译为一条intel 指令,如Godbolt's Compiler Explorer 所示

【讨论】:

【参考方案4】:

初始化和赋值是根本不同的事情。至于语言 C,你只需要接受它们是有区别的事实,但当然,这样定义是有技术原因的:

在许多系统中,您的可执行文件中可以有一个数据段。该段可以是读/写的,并给定一个初始化数组,如

uint8_t foo[] = 1, 2, 3, 4; // assume this has static storage duration

编译器可能只是决定将这个确切的字节序列直接输出到您的可执行文件中。所以根本没有代码在做赋值,当你的程序启动时,数据已经在内存中了。


OTOH,不能将数组分配给(仅分配给它们的单个成员)。这就是 C 的定义方式,有时很不幸。

【讨论】:

以上是关于在c中数组初始化的背景会发生啥?的主要内容,如果未能解决你的问题,请参考以下文章

数组啥情况下要初始化

数据结构--单向链表

C语言面试干货——指定数组初始化器(GCC手册解析)

C语言面试干货——指定数组初始化器(GCC手册解析)

从字符串文字初始化char数组时会发生什么?

C#在循环内多次重新初始化数组