结构体数组的初始化会初始化单个元素的所有成员,为什么?

5

为什么有人要这样做?更重要的是,这究竟是如何工作的?我曾经认为这会以某种方式创建一个只定义了第一个成员的三个结构体数组。我知道指针指向数组的第一个元素,我也可以理解这是如何工作的,但这是如何定义的却让我困扰!(gcc 4.8.4)

void do_something(const void *);

typedef struct{

int a;
char b;
int c;

} the_data_t;

int main(int argc, char *argv[])
{
   the_data_t my_data[] = {10, 'a', 30};
   do_something((const void *)my_data);
}

void do_something(const void *data)
{
   printf("data a: %d\ndata b: %c\ndata c: %d\n", ((the_data_t*)data)->a,
      ((the_data_t*)data)->b, ((the_data_t*)data)->c);
}

输出

数据a: 10
数据b: a
数据c: 30

不管怎样,我将其更改为以下内容...

int main(int argc, char *argv[])
{
   the_data_t my_data = {10, 'a', 30};
   do_something(&my_data);
}

1
你应该展示真实的代码。可能辅助函数是设计用于数组的。因此,如果一个数组包含至少一个元素,你应该将其声明为数组而不是单个结构体。 - Vlad from Moscow
2
无法仅初始化结构的一部分,只能全选或者不选。 - molbdnilo
4个回答

8
我本来以为这会创建一个只定义了第一个成员的三个结构体的数组,但实际上不是这样。它实际上创建了一个具有所有成员初始化的一个元素的数组。
引用C11,第§6.7.9章:
每个大括号括起来的初始化列表都有一个关联的当前对象。当没有指定设计ator时,按照当前对象的类型依次初始化当前对象的子对象:按递增下标顺序初始化数组元素,在声明顺序中初始化结构体成员,并且联合体的第一个命名成员。[...]
以及
每个设计ator列表都以与最近的大括号对关联的当前对象开始其描述。设计ator列表中的每个项目(按顺序)指定其当前对象的特定成员,并将下一个设计ator(如果有的话)的当前对象更改为该成员。设计ator列表结束时得到的当前对象是由以下初始化器初始化的子对象。
还有
如果子聚合或包含的联合体的初始化程序以左花括号开头,则由该括号及其匹配右花括号括起来的初始化程序初始化子聚合或包含的联合体的元素或成员。否则,从列表中仅取足够的初始化程序来解释子聚合或包含联合体的元素或成员的数量; [...]
基本上,你的代码应该理想地看起来像
the_data_t my_data[] = {{10, 'a', 30}}; 

将一个元素初始化可视化。

另一方面,也可以通过以下方式来实现您期望的效果:

 the_data_t my_data[] = {{10}, {'a'}, {30}};

它创建了一个由3个元素组成的数组,所有元素都有成员变量a进行了初始化。


话虽如此,

 the_data_t my_data[] = {{10, 'a', 30}};

等价于编写

 the_data_t my_data = {10, 'a', 30};

除了这部分外,my_data 将不再是一个数组(但通常情况下,一个只有一个元素的数组有什么好处呢?)。

1
"一个只有一个元素的数组,通常有什么用呢?" --> 它允许代码通过 "引用" 传递 GMP 示例。研究一下 mpz_t 是如何声明的。 - chux - Reinstate Monica
这个答案基本上是正确的,但如果它还引用了C2011 6.7.9/20,那就会更清晰明了,因为它明确描述了成员初始化器周围没有大括号的情况下的语义,正如在这里观察到的那样。 - John Bollinger

3
编译器将会处理以下内容:
the_data_t my_data[] = {10, 'a', 30};   

as

the_data_t my_data[1] = {{ 10, 'a', 30 }}; // Though it will raise warning.  

因此,my_data 是一个 the_data_t 类型的数组。

这类似于声明一个二维数组的方式:

int a[][3] = { 1, 2, 3 }; 

那么编译器将把它视为。
int a[1][3] = { { 1, 2, 3 } }; 

打印a的大小,你会得到12(如果该机器上int的大小为4)。


显然,使用的特定编译器确实按照您描述的方式解释初始化程序,但我很难看出这种行为是如何被标准描述的。 - John Bollinger
@JohnBollinger; 这在第6.7.9/20节中有描述:如果子聚合或包含的联合体的初始化程序以左大括号开头,则由该大括号及其匹配的右大括号括起来的初始化程序将初始化子聚合或包含的联合体的元素或成员。否则,仅从列表中取足够的初始化程序来说明子聚合或包含联合体的元素或成员;任何剩余的初始化程序都留给初始化当前子聚合体或[...]的下一个元素或成员的聚合体。 - haccks

2

这不是正确的写法。GCC会对此发出警告:

warning: missing braces around initializer
warning: (near initialization for ‘my_data[0]’)

它将创建一个只有1个元素的数组。

GCC确实会发出警告,我同意它的警告,并且也同意您的看法,即省略成员初始化程序周围的大括号是不好的风格。但这只是全部。从符合标准的意义上讲,代码是完全正确的。 - John Bollinger

2

如果你使用警告开关,gcc会对这个问题给出警告。

$ gcc -Wall test.c 
test.c: In function ‘main’:
test.c:14:25: warning: missing braces around initializer [-Wmissing-braces]
 the_data_t my_data[] = {10, 'a', 30};
                        ^
test.c:14:25: note: (near initialization for ‘my_data’)
$ 

为了解决这个问题,您可以正确初始化数组如下:
the_data_t my_data[] = {{10, 'a', 30}};

或者,就像您在帖子中展示的那样,您可以将my_data更改为结构变量。

the_data_t my_data = {10, 'a', 30}; // And call do_something as
do_something((const void *)&my_data);

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