【问题标题】:Initialization of an array of structure initializes all member of a single element, why?结构数组的初始化会初始化单个元素的所有成员,为什么?
【发布时间】:2016-12-18 10:34:03
【问题描述】:

为什么有人会这样做?更好的是,这甚至是如何工作的?我会假设这会以某种方式创建一个由三个结构组成的数组,其中只定义了第一个成员。我意识到一个指针指向数组的第一个元素,我知道它是如何工作的,但是它的定义方式让我失望! (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);
}

【问题讨论】:

  • 你应该展示真实的代码。可能是辅助函数被设计为与数组一起使用。因此,如果一个数组只包含一个元素,则应将其声明为数组而不是单个结构。
  • 没有办法只初始化结构的一部分,要么全有,要么全无。

标签: c arrays syntax initialization structure


【解决方案1】:

我会假设这会以某种方式创建一个包含三个结构的数组,其中只定义了第一个成员

不,不是那样的。基本上它会创建一个包含一个元素的数组,其中所有成员都已初始化。

引用C11,第 6.7.9 章

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

和

每个指示符列表都以与其关联的当前对象开始其描述 最近的周围大括号对。指示符列表中的每个项目(按顺序)指定一个 其当前对象的特定成员,并将当前对象更改为下一个 指定符(如果有的话)成为该成员。150) 在结束时产生的当前对象 指示符列表是要由以下初始化程序初始化的子对象。

和

[...] 如果 子聚合或包含的联合以左大括号开头,初始化器由 该大括号及其匹配的右大括号初始化 子聚合或包含的联合。否则,列表中只有足够的初始化器是 考虑到子聚合的元素或成员或第一个成员 包含的联合; [...]

基本上,您的代码应该理想地看起来像

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

可视化初始化一个元素。

OTOH,你所期望的可能会实现

 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 将不再是一个数组(但一般来说,单元素数组有什么好处呢?)。

【讨论】:

  • “一般来说,单元素数组有什么好处,” --> 它允许代码通过“引用”GMP example 研究mpz_t 是如何声明的。
  • 这个答案基本上是正确的,但如果它还引用了 C2011 6.7.9/20 会更清楚,它明确描述了成员初始化器周围没有大括号的情况下的语义,正如这里所观察到的那样。
【解决方案2】:

编译器会处理

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

作为

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)。

【讨论】:

  • 很明显,正在使用的特定编译器确实按照您的描述解释初始化程序,但我无法看到标准如何描述该行为。
  • @JohnBollinger;这在 6.7.9/20 节中有所描述:如果子聚合或包含联合的初始化程序以左大括号开头,则由该大括号括起来的初始化程序及其匹配的右大括号初始化元素或子聚合或包含的联合的成员。否则,仅从列表中获取足够多的初始化器来考虑子聚合的元素或成员或包含的联合的第一个成员;剩下的任何初始化器都将用于初始化当前子聚合或 [...] 的聚合的下一个元素或成员。
【解决方案3】:

这是不对的。 GCC 会给你一个警告:

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

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

【讨论】:

  • GCC 确实发出警告,我同意这一点,也同意你在成员初始化器周围省略大括号的做法很糟糕。但这就是全部。从符合标准的意义上说,该代码是完全正确的。
【解决方案4】:

如果你使用警告开关,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);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多