c中数组初始化的后台发生了什么?
What happens in the background of array initialization in c?
鉴于这是合法的
uint8_t bytes[4] = { 1, 2, 3, 4 };
这不是:
uint8_t bytes2[4];
bytes2 = { 1, 2, 3, 4 };
{ 1, 2, 3, 4 }
代表什么?
我假设它既不是右值也不是左值。预处理器代码糖果可以扩展成什么东西?
像{1,2,3,4};
这样的语法被称为大括号括起来的初始化列表,它是一个初始化器。只能用于初始化(数组类型)
引用 C11
,章节 §6.7.9
- P11
The initializer for a scalar shall be a single expression,
[数组不是标量类型,所以不适用于我们]
- P14
An array of character type may be initialized by a character string literal or UTF−8 string
literal, optionally enclosed in braces.
[我们这里没有使用字符串字面量,所以也不适用]
- P16
Otherwise, the initializer for an object that has aggregate or union type shall be a brace-enclosed
list of initializers for the elements or named members.
[这是我们感兴趣的情况]
和,P17,
Each brace-enclosed initializer list has an associated current object. When no
designations are present, subobjects of the current object are initialized in order according
to the type of the current object: array elements in increasing subscript order, structure
members in declaration order, and the first named member of a union.[....]
所以,在这里,大括号括起来的列表中的值并没有直接"assigned"到数组,它们被用来初始化数组的各个成员。
OTOH 是一种数组类型,不是 可修改的左值,因此无法对其进行赋值。换句话说,数组变量不能用作赋值运算符的LHS。
详细说明,来自 C11
,章节 §6.5.16
assignment operator shall have a modifiable lvalue as its left operand.
初始化和赋值是根本不同的事情。至于C语言,你只需要接受它们有区别的事实,当然,它这样定义是有技术原因的:
在许多系统中,您的可执行文件中可以有一个数据段。该段可以是 read/write 并给出一个初始化数组,如
uint8_t foo[] = {1, 2, 3, 4}; // assume this has static storage duration
编译器可以决定将这个确切的字节序列直接输出到您的可执行文件中。所以根本没有代码做一个赋值,程序启动时数据已经在内存中了。
OTOH,无法将数组分配给(只能分配给它们的个别成员)。 C就是这样定义的,有时很不幸。
{1,2,3,4}
是一个初始化列表。它可用于指定具有至少 4 个元素的对象的初始值,无论是数组元素还是结构成员,包括嵌套对象的元素。
您不能使用此语法为数组赋值,如下所示:
bytes2 = {1,2,3,4};
因为不支持语法,而且数组不是左值。
您可以使用初始化器列表作为称为复合文字的 C99 语法的一部分来创建对象并将它们用作赋值的右值、return 值或函数参数:
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
编译为单个英特尔指令,如在 Godbolt's Compiler Explorer
上所见
{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
鉴于这是合法的
uint8_t bytes[4] = { 1, 2, 3, 4 };
这不是:
uint8_t bytes2[4];
bytes2 = { 1, 2, 3, 4 };
{ 1, 2, 3, 4 }
代表什么?
我假设它既不是右值也不是左值。预处理器代码糖果可以扩展成什么东西?
像{1,2,3,4};
这样的语法被称为大括号括起来的初始化列表,它是一个初始化器。只能用于初始化(数组类型)
引用 C11
,章节 §6.7.9
- P11
The initializer for a scalar shall be a single expression,
[数组不是标量类型,所以不适用于我们]
- P14
An array of character type may be initialized by a character string literal or UTF−8 string literal, optionally enclosed in braces.
[我们这里没有使用字符串字面量,所以也不适用]
- P16
Otherwise, the initializer for an object that has aggregate or union type shall be a brace-enclosed list of initializers for the elements or named members.
[这是我们感兴趣的情况]
和,P17,
Each brace-enclosed initializer list has an associated current object. When no designations are present, subobjects of the current object are initialized in order according to the type of the current object: array elements in increasing subscript order, structure members in declaration order, and the first named member of a union.[....]
所以,在这里,大括号括起来的列表中的值并没有直接"assigned"到数组,它们被用来初始化数组的各个成员。
OTOH 是一种数组类型,不是 可修改的左值,因此无法对其进行赋值。换句话说,数组变量不能用作赋值运算符的LHS。
详细说明,来自 C11
,章节 §6.5.16
assignment operator shall have a modifiable lvalue as its left operand.
初始化和赋值是根本不同的事情。至于C语言,你只需要接受它们有区别的事实,当然,它这样定义是有技术原因的:
在许多系统中,您的可执行文件中可以有一个数据段。该段可以是 read/write 并给出一个初始化数组,如
uint8_t foo[] = {1, 2, 3, 4}; // assume this has static storage duration
编译器可以决定将这个确切的字节序列直接输出到您的可执行文件中。所以根本没有代码做一个赋值,程序启动时数据已经在内存中了。
OTOH,无法将数组分配给(只能分配给它们的个别成员)。 C就是这样定义的,有时很不幸。
{1,2,3,4}
是一个初始化列表。它可用于指定具有至少 4 个元素的对象的初始值,无论是数组元素还是结构成员,包括嵌套对象的元素。
您不能使用此语法为数组赋值,如下所示:
bytes2 = {1,2,3,4};
因为不支持语法,而且数组不是左值。
您可以使用初始化器列表作为称为复合文字的 C99 语法的一部分来创建对象并将它们用作赋值的右值、return 值或函数参数:
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
编译为单个英特尔指令,如在 Godbolt's Compiler Explorer
{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