【问题标题】:Can someone please explain the difference between these two initiliazers?有人可以解释这两种启动器之间的区别吗?
【发布时间】:2017-07-10 19:06:11
【问题描述】:

我想知道是否有人可以对以下两段代码之间的差异提供详细、简单的解释。给定以下定义:

typedef struct {
    stuff;
    stuff_2;
} variable_t;

有什么区别:

  • variable_t my_variable;
  • variable_t my_variable = {};

如果我执行第一个,然后从未完全初始化它,为什么编译器不会抛出错误?

注意:我正在使用gcc -std=gnu99 进行编译,因此第二个是有效的,并且最终解决了我遇到的问题。我想知道为什么。

【问题讨论】:

  • 不同的是,第一个没有初始化,第二个是(归零)。编译器不会抛出错误,因为它假定您正在执行不方便更清楚表达的特定操作。
  • 第二个(Gnu C 接受)不是初始化为零,而是初始化为每个字段默认值(好吧,在 C 中可能是零)。
  • 似乎仅使用 [c] 标签无法回答此问题。
  • @GrzegorzSzpetkowski 抱歉,我应该指定我正在使用 gnu99 编译器标志。添加标签
  • 最好只使用可移植的variable_t my_variable = { 0 };。 IIRC,gcc 没有记录空大括号行为,它似乎可以工作。

标签: c gcc gnu99


【解决方案1】:

这在一定程度上取决于您放置相应变量定义的位置,而且似乎也取决于使用的编译器。

自动存储时长

让我们讨论一下变量具有自动存储持续时间时的区别(如果您将它放在函数或块范围内并且没有static关键字):

void someFunction() {
   variable_t my_variable;       // (1)
   variable_t my_variable = {};  // (2)
}

(1) 表示没有显式初始化的变量定义。而根据这个online C standard draft,它的价值是不确定的:

如果具有自动存储持续时间的对象未初始化 明确地说,它的值是不确定的。

(2) 是一个变量定义,通过没有指示符的初始化器列表进行显式初始化,即不通过成员的名称将值与成员关联,而仅通过值的顺序(参见6.7.9 p17..21)。

有趣的段落是6.7.9 p21,它指出如果初始化列表的条目少于结构成员的数量,则根据静态存储持续时间的初始化规则对成员进行初始化(即0或@987654330 @ 后面会解释):

如果大括号括起来的列表中的初始值设定项比那里少 是聚合的元素或成员,...,其余的 聚合应隐式初始化,与对象相同 具有静态存储持续时间。

所以看起来如果你写variable_t my_variable = {},那么所有的成员都会被初始化为0或者NULL

然而,正如 aschepler 在 cmets 中提到的,C initialization list grammar 声明初始化列表不能为空(另请参见 cppreference.com):

...初始化器必须是非空的,用大括号括起来的, 成员的初始值设定项的逗号分隔列表

所以根据标准,C 中的初始化列表至少需要一个条目;在我的 XCode8.3 环境中使用-std=gnu99 对其进行测试时,似乎支持一个空的初始化列表,但我知道这不是一个有效的参考。所以为了安全起见,不依赖于特定的编译器扩展,你实际上应该这样写:

   variable_t my_variable = {0};  

静态存储时长

在文件范围内,您的变量定义将具有静态存储持续时间,然后应用其他规则(参见6.7.9 (10)):

(10) ... 如果具有静态或线程存储持续时间的对象是 未显式初始化,则:

  • 如果有指针类型,则初始化为空指针;
  • 如果它具有算术类型,则将其初始化为(正或无符号)零;
  • 如果是聚合,则每个成员都根据这些规则进行初始化(递归),并且任何填充都初始化为零位;
  • 如果是联合,则根据这些规则(递归)初始化第一个命名成员,并初始化任何填充 零位;

...

(21) 如果大括号括起来的列表中的初始值设定项少于聚合的元素或成员,...聚合的其余部分应被隐式初始化,与具有静态存储持续时间的对象相同。

所以如果你写...

#include <stdio.h>
variable_t my_variable;       // (1)
variable_t my_variable = {};  // (2)

那么 (1) 和 (2) 实际上会产生相同的结果,因为对于未显式初始化的变量 (1),适用第 (10) 段,而对于显式但为空的初始化变量 (2),根据第 (21) 段),每个成员都回退到 (10) 的初始化规则。

同样,编译器可能不支持上面讨论的空初始化列表。

希望它有所帮助(因为它已经输入了很多 :-))

【讨论】:

  • {} 是非法的,因为 6.7.9 开头的语法定义:initializer 要么是 assignment-expression,要么序列{initializer-list},或序列{initializer-list,}initializer-list i> 没有空的产生式。
  • {0} 总是产生相同的结果,无论第一个成员的类型如何。还应该注意的是,仅从 C11 起才需要对填充位进行归零。
【解决方案2】:

当您声明时:

variable_t my_variable;           // a variable_t that is uninitialized

variable_t my_variable = {};      // a variable_t initialized with zeroes.

请注意,对于在文件范围内声明的变量,这并不重要,因为数据通常会在程序启动之前清零。

在堆栈上使用,第二行有效地用零填充 my_variables。就像有一个调用一样:

memset(&amp;variable, 0, sizeof(variable));

这是可行的,因为在 C 中,您可以使用 = 复制 struct

这是一个电脑肯定会赢的小游戏。

struct A { /*...*/ };

void main() 
{
   A a;  // random
   A b = {};
   if (memcmp(&a, &b, sizeof(A)) == 0)
   {
       printf("You win\n");
       return;
   }

   a = b;
   if (memcmp(&a, &b, sizeof(A)) == 0)
   {
       printf("I win\n");
   }
}

【讨论】:

  • 请注意,如果它是在文件范围内定义的,第一个可能会初始化为全零,但如果它是在块范围内定义的,则不会。第二个在严格的标准 C 中是不合法的——它是 C++ 表示法(一些 C 编译器可能允许,即使标准不允许)。
  • 当然在第一个memcmp(&amp;a,... 中使用未初始化的变量如A a(不是unsigned char)是UB。
  • 它的地址被初始化 A 在栈上。我可以阅读它。
  • @DougSkinner:好的——我见过。我的评论仍然准确:= {}; 初始化程序仍然不是标准 C,但它被一些 C 编译器接受,特别是被 GCC 和可能被 Clang 模拟 GCC 所接受。对于默认初始化变量来说,这不是一个糟糕的符号;它不是标准的 C。
  • 使用memset 为零不能保证提供与{0} 初始化器相同的值,尽管这是非常常见的行为。 C 标准保证零字节序列为所有整数类型提供零值,但没有说明空指针值或零浮点类型值的表示。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-10
  • 2019-05-14
  • 2021-07-18
相关资源
最近更新 更多