鉴于这是合法的
uint8_t bytes[4] = { 1, 2, 3, 4 };
这不是:
uint8_t bytes2[4];
bytes2 = { 1, 2, 3, 4 };
{ 1, 2, 3, 4 }
代表什么?
假设它既不是右值也不是左值。一个预处理器代码糖果,扩展到什么?
答案 0 :(得分:8)
像{1,2,3,4};
这样的语法称为大括号括号初始化列表,它是初始值设定项。它只能用于初始化(对于数组类型)。
引用C11
,章节§6.7.9
标量的初始值设定项应为单个表达式,
[数组不是标量类型,因此不适用于我们]
字符类型数组可以用字符串文字或UTF-8字符串初始化 文字,可选择括在括号中。
[我们这里没有使用字符串文字,因此也不适用于我们]
否则,具有聚合或联合类型的对象的初始值设定项应为括号括起来的 元素或命名成员的初始值设定项列表。
[这是我们感兴趣的案例]
和,P17,
每个大括号括起来的初始化列表都有一个关联的当前对象。什么时候没有 如果存在名称,则按照顺序初始化当前对象的子对象 到当前对象的类型:增加下标顺序的数组元素,结构 声明顺序的成员,以及工会的第一个指定成员。[....]
所以,这里,括号括起来的列表中的值不是直接“赋值”给数组的,它们用于初始化数组的各个成员。
OTOH,一种数组类型,不可修改的左值,因此无法分配。换句话说,数组变量不能用作赋值运算符的LHS。
详细说明,来自C11
,章节§6.5.16
赋值运算符应具有可修改的左值作为其左操作数。
答案 1 :(得分:7)
{1,2,3,4}
是一个初始化列表,一个特殊的语法标记,只能在声明数组的行上使用。
这完全由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
答案 2 :(得分:5)
初始化和分配是根本不同的事情。至于语言C,你只需要接受它们是有区别的事实,但当然,有一个技术原因是这样定义的:
在许多系统中,您可以在可执行文件中包含数据段。这段可以读/写,并给出一个初始化的数组,如
uint8_t foo[] = {1, 2, 3, 4}; // assume this has static storage duration
编译器可以决定将这个确切的字节序列直接输出到您的可执行文件中。所以根本没有代码进行分配,程序启动时数据已经存在于内存中。
OTOH,无法将数组分配给(仅限其个别成员)。这就是C的定义方式,有时候很不幸。
答案 3 :(得分: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
编译为单个英特尔指令,可在Godbolt's Compiler Explorer上看到