初始化对象时,{0}是什么意思?


252

什么时候{0}用于初始化对象,这是什么意思?我在任何{0}地方都找不到引用,而且由于花括号,Google搜索没有帮助。

示例代码:

SHELLEXECUTEINFO sexi = {0}; // what does this do?
sexi.cbSize = sizeof(SHELLEXECUTEINFO);
sexi.hwnd = NULL;
sexi.fMask = SEE_MASK_NOCLOSEPROCESS;
sexi.lpFile = lpFile.c_str();
sexi.lpParameters = args;
sexi.nShow = nShow;

if(ShellExecuteEx(&sexi))
{
    DWORD wait = WaitForSingleObject(sexi.hProcess, INFINITE);
    if(wait == WAIT_OBJECT_0)
        GetExitCodeProcess(sexi.hProcess, &returnCode);
}

没有它,以上代码将在运行时崩溃。

Answers:


302

这里发生的事情称为聚合初始化。这是ISO规范8.5.1节中汇总的(缩写)定义:

聚合是没有用户声明的构造函数,没有私有或受保护的非静态数据成员,没有基类,也没有虚函数的数组或类。

现在,使用{0}这样的初始化聚合基本上是0整件事的窍门。这是因为使用聚合初始化时,您不必指定所有成员,并且规范要求所有未指定的成员都进行默认初始化,这意味着将其设置0为简单类型。

这是规范中的相关报价:

如果列表中的初始化程序少于聚合中的成员,则每个未显式初始化的成员都应进行默认初始化。例:

struct S { int a; char* b; int c; };
S ss = { 1, "asdf" };

初始化ss.a1ss.b"asdf",和ss.c与所述形式的表达的值int(),即, 0

您可以在此处找到有关此主题的完整规范


15
反应出色。只是想补充一下,使用{0}初始化聚合与仅使用{}初始化聚合相同。也许前者使内置类型更容易被清零。
詹姆斯·霍普金

7
一些编译器对{}感到cho

10
在C ++中,如果第一个成员不能为零构造,则{0}将不起作用。例如:struct A {B b; 我 字符c; }; 结构B {B(); B(字符串); }; A a = {}; //此语句不能重写为'A a = {0}'。
亚伦

24
@Branan,这是因为在C中,“ {}”无效。在C ++中是。@ don.neufeld,这在C ++ 03中已更改(默认初始化->值初始化)。请记住,引用引用了C ++ 98标准。
Johannes Schaub-litb

3
那么,这是否取代了使用ZeroMemory()的需要(在VC ++中)?
2014年

89

要注意的一件事是,该技术不会将填充字节设置为零。例如:

struct foo
{
    char c;
    int  i;
};

foo a = {0};

与以下内容不同:

foo a;
memset(&a,0,sizeof(a));

在第一种情况下,c和i之间的填充字节未初始化。你为什么要在乎?好吧,如果要将这些数据保存到磁盘或通过网络或其他方式发送,则可能会遇到安全问题。


13
当然,如果您执行“ write(f,&a,sizeof(a))”,这仅是一个安全问题,它可以在不同的处理器/编译器上产生不同的文件编码。没有memset的格式正确的输出将是安全的。
亚伦

3
另外,如果您要通过网络发送内容,则始终会设置要打包的对齐方式。这样,您将获得尽可能少的额外填充字节。
马克·凯格尔

18
应该注意的是,尽管规范不要求初始化填充,但是任何精明的编译器都会这样做,因为仅花费时间在“周围”进行初始化。
托马斯

4
我想对“不是”一词的使用表示怀疑。填充字节是实现定义的。编译器可以随意将foo a = {0}转换为memset(&a,0,sizeof(a))。不需要“跳过”填充字节,只需设置foo.c和foo.i。为(潜在的)安全漏洞+1
SecurityMatt

3
@Leushenko第19点说“所有未显式初始化的子对象”,所以我倾向于第21点(您引用的内容)草率。如果规范允许填充在最后一个初始化器之前一直未初始化,那将是真正的奇怪,在此之后必须将填充置零,尤其是考虑到使用指定的初始化器时初始化器可能会出现乱序。
MM

20

请注意,空的聚合初始化程序也可以工作:

SHELLEXECUTEINFO sexi = {};
char mytext[100] = {};

11

回答为什么ShellExecuteEx()崩溃:您的SHELLEXECUTEINFO“ sexi”结构有许多成员,而您只初始化其中一些成员。

例如,该成员sexi.lpDirectory可能指向任何地方,但ShellExecuteEx()仍将尝试使用它,因此您将遇到内存访问冲突。

当您包括该行时:

SHELLEXECUTEINFO sexi = {0};

在进行其余结构设置之前,您要告诉编译器在初始化您感兴趣的特定成员之前将所有结构成员归零。ShellExecuteEx()知道如果sexi.lpDirectory为零,则应忽略它。


7

我也用它来初始化字符串。

char mytext[100] = {0};

5
当然,如果使用mytext作为字符串,则char mytext [100]; mytext [0] ='\ 0'; 给出空字符串的效果相同,但只会使实现将第一个字节清零。
克里斯·杨(

@Chris:我经常希望有一种用于部分对象初始化的语法。能够“声明和初始化” x,然后依次对y和z进行处理,比必须先声明x,y和z,然后对x,y和z进行初始化要好得多,但是仅初始化100字节就好了。实际上需要初始化的人似乎很浪费。
超级猫

7

{0}在C和C ++中,对于任何(完整对象)类型都是有效的初始化器。这是用于将对象初始化为零的常见用法(请继续阅读以了解其含义)。

对于标量类型(算术和指针类型),花括号是不必要的,但明确允许使用它们。引用ISO C标准的N1570草案第6.7.9节:

标量的初始值设定项应为单个表达式,可以选择用大括号括起来。

它将对象初始化为零(0对于整数,0.0对于浮点,对于指针为空指针)。

对于非标量类型(结构,数组,联合),{0}指定将对象的第一个元素初始化为零。对于包含结构,结构数组等的结构,这是递归应用的,因此第一个标量元素将设置为零,以适合类型。与任何初始化程序一样,任何未指定的元素都设置为零。

中间括号({})可以被省略; 例如,这两个都是有效且等效的:

int arr[2][2] = { { 1, 2 }, {3, 4} };

int arr[2][2] = { 1, 2, 3, 4 };

这就是为什么您不必编写例如{ { 0 } }第一个元素为非标量的类型的原因。

所以这:

some_type obj = { 0 };

是初始化obj为零的简便方法,这意味着的每个标量子对象obj都设置为0整数,0.0浮点数或null指针(如果是指针)。

对于C ++,规则相似。

在您的特定情况下,由于您要向sexi.cbSize等等分配值,因此很明显这SHELLEXECUTEINFO是结构或类类型(或可能是并集,但可能不是),因此并非所有情况都适用,但正如我所说的,这{ 0 }是常见的可以在更一般的情况下使用的成语。

这是(必须的)等价于使用memset该对象的表示设置为所有位零。浮点数0.0或空指针都不一定表示为全零位,并且{ 0 }初始化程序不必将填充字节设置为任何特定值。但是,在大多数系统上,它可能会产生相同的效果。


1
在C ++中,{0}对于没有构造函数接受的对象,它不是有效的初始化器0;或第一个元素本身是这样的集合(或没有元素的集合)
MM

3

自从我在c / c ++中工作以来已经有一段时间了,但是IIRC,同样的快捷方式也可以用于数组。


2

我一直想知道,为什么您应该使用类似

struct foo bar = { 0 };

这是一个测试用例来解释:

检查

struct f {
    int x;
    char a;
} my_zero_struct;

int main(void)
{
    return my_zero_struct.x;
}

我编译gcc -O2 -o check check.c后输出符号表readelf -s check | sort -k 2(这是x64系统上ubuntu 12.04.2上的gcc 4.6.3)。摘抄:

59: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
48: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
25: 0000000000601018     0 SECTION LOCAL  DEFAULT   25 
33: 0000000000601018     1 OBJECT  LOCAL  DEFAULT   25 completed.6531
34: 0000000000601020     8 OBJECT  LOCAL  DEFAULT   25 dtor_idx.6533
62: 0000000000601028     8 OBJECT  GLOBAL DEFAULT   25 my_zero_struct
57: 0000000000601030     0 NOTYPE  GLOBAL DEFAULT  ABS _end

这里的重要部分my_zero_struct是在之后__bss_start。C程序中的“ .bss”部分是内存的一部分,之前 main被设置为零,然后称为.bss上的Wikipedia

如果将上面的代码更改为:

} my_zero_struct = { 0 };

然后,生成的“ check”可执行文件至少与ubuntu 12.04.2上的gcc 4.6.3编译器看起来完全相同;在my_zero_struct仍处于.bss部分,因此它会被自动初始化为零,之前main被调用。

注释中的提示memset可能会初始化“完全”结构也不是一个改进,因为该.bss部分已完全清除,这也意味着“完全”结构被设置为零。

可能是C语言标准并没有提及任何这一点,但在现实世界中的C编译器我从来没有见过一个不同的行为。


全局变量和静态变量始终默认初始化为0或默认ctor。但是,如果在本地声明f的实例,则可能会得到不同的结果。
Logman

-5

{0}是一个匿名数组,其元素为0。

这用于用0初始化数组的一个或所有元素。

例如int arr [8] = {0};

在这种情况下,arr的所有元素都将初始化为0。


4
{0}不是匿名数组。它甚至不是一种表达。这是一个初始化程序。
基思·汤普森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.