C89中的{0}初始化器有多标准化?

25

在我的当前项目中,我们使用MISRA 2004标准,使用三个GCC编译器版本3.2.3、4.4.2和5.4.0。

我们使用严格检查构建,包括pedantic开关、c89标准和许多其他限制。其中之一是所有数据必须在声明时初始化。

问题在于,在GCC 3.2.3上,通用的零初始化器{0}只适用于基本单元类型的数组。如果我有一个结构体数组,那么我会得到一个缺少大括号的警告,只有当我将{0}更改为{{0}}时才能消除警告。

struct my_type my_thing[NUMBER_OF_THINGS] = {0};
变成。
struct my_type my_thing[NUMBER_OF_THINGS] = {{0}};

但是,当处理具有结构成员的结构数组时,这种方法无法使用。这时问题出在 4.4.2 编译器上,会出现缺少初始化程序的错误。因此,我不得不这样做:

struct my_struct_with_structs_inside my_other_thing[NUMBER_OF_THINGS] = {{0, 0, {0}, 0}};

这样的代码可以通过编译器,但会触发我们的MISRA检查器,因为MISRA要求使用通用的单一{0}初始化程序或完整的整个数组初始化程序:

struct my_struct_with_structs_inside my_other_thing[NUMBER_OF_THINGS] = {{0, 0, {0}, 0},
                                                                         {0, 0, {0}, 0},
                                                                         {0, 0, {0}, 0},
                                                                         {0, 0, {0}, 0},
                                                                         {0, 0, {0}, 0}};

由于我们有各种限制,NUMBER_OF_THINGS可能会在构建时从源代码外部自动生成并发生更改,因此这对我们来说是不切实际的。

核心问题

我想告诉我的老板所谓的通用初始化器 {0} 可以用于初始化任何数组。我发现在GCC邮件列表和Bugzilla上有很多年的帖子都认为我提到的编译器警告是错误,并且称 {0} 是标准的一部分。然而,它们中没有一个提到哪个标准,我也无法在ISO C89或C99草案或K&R v2中找到 {0}{0} 是标准吗? 有没有办法保证一个具有结构成员的结构体数组被初始化为全零(或NULL)?

问题在于,虽然我可以消除MISRA代码的违规情况,但我不确定这样做:

struct my_struct_with_structs_inside my_other_thing[NUMBER_OF_THINGS] = {{0, 0, {0}, 0}};

使用该函数足以确保数组完全清零。

有人能从问题的根源提供智慧吗?


1
你的代码必须在GCC 3.2.3或GCC 4.4.2下编译吗?你说{0}在GCC 3.2.3下无法编译。如果你的代码必须在GCC 3.2.3下编译,那么无论它们有多标准化,你都不能使用那些在GCC 3.2.3下无法编译的东西。 - user2357112
3个回答

24
{0} 初始化器可用于初始化所有聚合类型(即数组或结构体)中的对象,在 C 的任何版本中都是 100% 标准的。语法允许您省略子聚合物的大括号。
我不会在这里详细介绍所有细节。如果你对正式的规范文本感兴趣,可以阅读例如 C11 6.7.9 第17节及其后面的一些复杂规则进行解释。
关于MISRA-C:2004,这个规则有点繁琐,而且还有一个关于它的MISRA-C:2004 TC1。您的静态分析器可能没有正确实现TC1 9.2,该规则指出顶级{0}是符合要求的。
然而,TC1并没有完全解决所有问题。我在2008年向委员会询问了这个问题。

https://www.misra.org.uk/forum/viewtopic.php?f=65&t=750

我在那里得到的回复是正式委员会的回应,可以用作您文档的参考。 委员会同意规则需要进一步改进,并基于此,在MISRA-C:2012中修复了该规则,其中{0}可用于初始化聚合类型的子对象的任何位置。
如果可能,我建议使用MISRA-C:2012。

7

是的,{0} 在C89中也是 合法的通用初始化器

如果初始化列表中的初始化器比聚合类型的成员更少,则该聚合的剩余部分将被隐式初始化,与具有静态存储期的对象相同。

这在所有更新版本的标准中都是正确的。

从gcc中得到的警告是一个旧的bug:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80454,该问题已在gcc 4.9左右得到解决。

对于你的情况,你可以考虑:

  • 升级到更高版本的gcc(3.2.3太老了!)
  • 使用-Wno-missing-braces选项来消除警告,因为这是一个已知的bug
  • 使用memset来初始化您的结构类型

2
警告:如果代码在某些奇特的系统上运行,memset将无法正确初始化具有指针成员的结构体,如果该系统使用非零位模式的NULL指针。 - user694733
我有一种感觉,MISRA 不会赞成对结构体进行 memset。 - M.M
8
遵循MISRA规则的人正在从事安全关键软件工作。“非常不可能”不是正确性的可接受标准,会导致人员死亡。 - Eric Postpischil
3
没有人说这是一个可接受的标准。它被建议作为OP当前情况下潜在的选项之一。我还链接了相关信息,由OP和其他人决定是否适合他们。此外,答案不仅仅是给OP看的-其他在非MISRA环境中编写代码的人也可能遇到相同/类似的问题。但请随意对我提起刑事诉讼。 - P.P
3
对于浮点成员也是如此。不能保证全零位模式代表浮点值为零。 - Ian Abbott
显示剩余6条评论

3
"通用初始化器" {0} 应该可以工作。对于C89语言,3.5.7节的相关段落如下: 否则,具有聚合类型的对象的初始化器应该是一个用大括号括起来的列表,按照下标或成员顺序增加来初始化聚合体的成员;并且具有联合类型的对象的初始化器应该是联合体的第一个成员的括号括起来的初始化器。 如果聚合体包含了聚合体或者联合体的成员,或者如果联合体的第一个成员是一个聚合体或者联合体,则规则递归地应用到子聚合体或者包含的联合体中。如果子聚合体或者包含的联合体的初始化器以左花括号开头,则由该括号和其匹配的右花括号括起来的初始化器将初始化子聚合体或包含联合体的第一个成员。否则,只能取出列表中足够的初始值来初始化当前子聚合体或包含联合体的第一个成员;任何剩余的初始化器都留给初始化下一个成员的聚合体,即当前子聚合体或包含联合体所在的聚合体的下一个成员。 如果列表中的初始值少于聚合体的成员数量,则剩余部分的聚合体应该隐式地初始化为具有静态存储期限的对象一样。"

2
为了完整起见,“如果具有静态存储期的对象没有显式初始化,则会隐式初始化,好像分配了所有算术类型成员0并且分配了空指针常量作为每个具有指针类型成员。” - pipe

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