C ++零初始化 - 为什么是`B`在这个程序初始化,但`了`初始化?

问题描述 投票:124回答:4

根据公认的(唯一的)答案this Stack Overflow question

定义与构造

MyTest() = default;

将代替零初始化对象。

那么为什么以下,

#include <iostream>

struct foo {
    foo() = default;
    int a;
};

struct bar {
    bar();
    int b;
};

bar::bar() = default;

int main() {
    foo a{};
    bar b{};
    std::cout << a.a << ' ' << b.b;
}

产生这样的输出:

0 32766

定义的所有构造函数是默认?对?而对于POD类型,默认的初始化为零初始化。

而根据对this question接受的答案,

  1. 如果POD构件没有在构造也不经由C ++ 11初始化在类初始化,这是缺省初始化。
  2. 答案是一样的,不管栈或堆。
  3. 在C ++ 98(而不是之后),新的INT()被指定为执行零初始化。

尽管试图环绕default constructorsdefault initialization我的(尽管是微小的)头,我不能拿出一个解释。

c++ initialization language-lawyer
4个回答
105
投票

这里的问题是相当微妙的。你会认为,

bar::bar() = default;

会给你一个编译器生成的默认构造函数,这样做,但现在考虑用户提供的。 [dcl.fct.def.default]/5规定:

显式违约函数和隐式声明的函数被统称为默认的功能,并实现应为他们([class.ctor] [class.dtor],[class.copy.ctor],[class.copy.assign提供隐含的定义]),以删除这可能意味着定义它们。功能是用户提供的,如果它是用户声明并没有明确违约或首次声明中删除。由用户提供的显式默认的功能(即,它的第一个声明之后明确的缺省值)是在其被明确地默认点限定;如果为已删除这样的功能被隐含定义,是形成不良的节目。 [注:声明一个函数将其第一个声明之后为缺省的可提供有效的执行和简明的定义,同时使稳定的二进制接口到一个不断发展的代码库。 - 注完]

重点煤矿

因此,我们可以看到,因为你没有默认bar()当你第一次宣布它,它现在被认为是用户提供的。正因为如此[dcl.init]/8.2

如果T是一个没有用户提供的或删除的默认的构造(可能CV修饰)类型,则该对象是零初始化和缺省初始化的语义约束被检查,并且如果T具有非平凡缺省构造中,对象是缺省初始化;

不再适用,我们不看重初始化b而是默认初始化它每[dcl.init]/8.1

如果T是一个(可能是CV-合格)类型([类])与或者没有默认的构造([class.default.ctor])或默认的构造是用户提供的或删除,则该对象是缺省初始化;


25
投票

在行为的差异来自这一事实,即根据[dcl.fct.def.default]/5bar::bar是用户提供的,其中foo::foo是NOT1。因此,foo::foo将值初始化它的成员(意为:零初始化foo::a),但bar::bar会留uninitialized2。


1)[dcl.fct.def.default]/5

功能是用户提供的,如果它是用户声明并没有明确违约或首次声明中删除。

2)

[dcl.init#6]

到的类型T是指值初始化的对象:

  • 如果T是具有或者没有默认的构造([class.ctor])或默认的构造是用户提供的或删除的(可能CV修饰)类型,则该对象是缺省初始化;
  • 如果T是一个没有用户提供的或删除的默认的构造(可能CV修饰)类型,则该对象是零初始化和缺省初始化的语义约束被检查,并且如果T具有非平凡缺省构造中,对象是缺省初始化;
  • ...

[dcl.init.list]

对象或类型T的参考的列表的初始化被定义如下:

  • ...
  • 否则,如果初始化列表没有任何元素,T是一个类类型与一个默认的构造,对象是值初始化。

Vittorio Romeo's answer


10
投票

cppreference

集合初始化初始化聚集。这是列表初始化的一种形式。

聚集是以下类型之一:

[剪断]

  • 类类型[剪断],具有 [剪断](有变化对于不同的标准版本) 没有用户提供的,遗传的,或显式的构造(显式默认或删除构造允许) [剪断](有更多的规则,适用于这两个类)

根据这个定义,foo是一个聚合,而bar不是(它拥有用户提供的,非默认的构造函数)。

因此,对于fooT object {arg1, arg2, ...};是语法聚集的初始化。

集合初始化的效果是:

  • [剪断](一些细节无关这种情况下)
  • 如果初始化条款的数目小于成员或初始化列表的数目完全是空的,其余成员是值初始化。

因此a.a被初始化值,这对于int意味着零初始化。

对于bar,从另一方面T object {};是值初始化(类的实例,而不是成员的值初始化的!)。因为它是一个类类型有默认的构造函数,默认的构造函数被调用。你定义的缺省初始化成员(由于不具有构件initialisers的),其在int的情况下(与非静态存储)离开b.b与一个不确定的值默认构造函数。

而对于POD类型,默认的初始化为零初始化。

否。这是错误的。


附:你的实验和你的结论一句:眼见输出为零并不一定意味着该变量是零初始化。零是完全的垃圾可能值数。

对于我跑的程序,也许5〜6次,然后再发布到现在的10倍左右,一个始终为零。围绕小B的变化。

该值是相同的多次的事实,并不一定意味着它要么初始化。

我也试图与集(CMAKE_CXX_STANDARD 14)。其结果是一样的。

这一结果是与多个编译器选项相同的事实并不意味着变量初始化。 (虽然在某些情况下,改变标准版可以改变它是否被初始化)。

我如何能在某种程度上动摇我的RAM一点,所以,如果有零点那里,它现在应该是别的东西

目前在C ++中没有万无一失的方法,使未初始化值值出现非零。

后来才知道一个变量被初始化的方法是程序比较语言的规则和验证规则说,它被初始化。在这种情况下a.a确实被初始化。


0
投票

咩,我试图运行你作为test.cpp提供的片段,通过GCC&铛和多种优化级别:

steve@steve-pc /tmp> g++ -o test.gcc.O0 test.cpp
                                                                              [ 0s828 | Jan 27 01:16PM ]
steve@steve-pc /tmp> g++ -o test.gcc.O2 -O2 test.cpp
                                                                              [ 0s901 | Jan 27 01:16PM ]
steve@steve-pc /tmp> g++ -o test.gcc.Os -Os test.cpp
                                                                              [ 0s875 | Jan 27 01:16PM ]
steve@steve-pc /tmp> ./test.gcc.O0
0 32764                                                                       [ 0s004 | Jan 27 01:16PM ]
steve@steve-pc /tmp> ./test.gcc.O2
0 0                                                                           [ 0s004 | Jan 27 01:16PM ]
steve@steve-pc /tmp> ./test.gcc.Os
0 0                                                                           [ 0s003 | Jan 27 01:16PM ]
steve@steve-pc /tmp> clang++ -o test.clang.O0 test.cpp
                                                                              [ 1s089 | Jan 27 01:17PM ]
steve@steve-pc /tmp> clang++ -o test.clang.Os -Os test.cpp
                                                                              [ 1s058 | Jan 27 01:17PM ]
steve@steve-pc /tmp> clang++ -o test.clang.O2 -O2 test.cpp
                                                                              [ 1s109 | Jan 27 01:17PM ]
steve@steve-pc /tmp> ./test.clang.O0
0 274247888                                                                   [ 0s004 | Jan 27 01:17PM ]
steve@steve-pc /tmp> ./test.clang.Os
0 0                                                                           [ 0s004 | Jan 27 01:17PM ]
steve@steve-pc /tmp> ./test.clang.O2
0 0                                                                           [ 0s004 | Jan 27 01:17PM ]
steve@steve-pc /tmp> ./test.clang.O0
0 2127532240                                                                  [ 0s002 | Jan 27 01:18PM ]
steve@steve-pc /tmp> ./test.clang.O0
0 344211664                                                                   [ 0s004 | Jan 27 01:18PM ]
steve@steve-pc /tmp> ./test.clang.O0
0 1694408912                                                                  [ 0s004 | Jan 27 01:18PM ]

所以这是它有趣的,它清楚地表明铛O0打造的是阅读的随机数,大概堆栈空间。

我很快就变成了我的IDA,看看发生了什么:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  __int64 v4; // rax
  int result; // eax
  unsigned int v6; // [rsp+8h] [rbp-18h]
  unsigned int v7; // [rsp+10h] [rbp-10h]
  unsigned __int64 v8; // [rsp+18h] [rbp-8h]

  v8 = __readfsqword(0x28u); // alloca of 0x28
  v7 = 0; // this is foo a{}
  bar::bar((bar *)&v6); // this is bar b{}
  v3 = std::ostream::operator<<(&std::cout, v7); // this is clearly 0
  v4 = std::operator<<<std::char_traits<char>>(v3, 32LL); // 32 = 0x20 = ' '
  result = std::ostream::operator<<(v4, v6); // joined as cout << a.a << ' ' << b.b, so this is reading random values!!
  if ( __readfsqword(0x28u) == v8 ) // stack align check
    result = 0;
  return result;
}

现在有哪些呢bar::bar(bar *this)呢?

void __fastcall bar::bar(bar *this)
{
  ;
}

嗯,什么都没有。我们不得不求助于使用汇编:

.text:00000000000011D0                               ; __int64 __fastcall bar::bar(bar *__hidden this)
.text:00000000000011D0                                               public _ZN3barC2Ev
.text:00000000000011D0                               _ZN3barC2Ev     proc near               ; CODE XREF: main+20↓p
.text:00000000000011D0
.text:00000000000011D0                               var_8           = qword ptr -8
.text:00000000000011D0
.text:00000000000011D0                               ; __unwind {
.text:00000000000011D0 55                                            push    rbp
.text:00000000000011D1 48 89 E5                                      mov     rbp, rsp
.text:00000000000011D4 48 89 7D F8                                   mov     [rbp+var_8], rdi
.text:00000000000011D8 5D                                            pop     rbp
.text:00000000000011D9 C3                                            retn
.text:00000000000011D9                               ; } // starts at 11D0
.text:00000000000011D9                               _ZN3barC2Ev     endp

所以,是的,它只是,什么都没有,什么构造通常做的就是this = this。但我们知道,它实际上是随机加载未初始化的堆栈地址,并打印出来。

如果我们明确提出了两个结构值?

#include <iostream>

struct foo {
    foo() = default;
    int a;
};

struct bar {
    bar();
    int b;
};

bar::bar() = default;

int main() {
    foo a{0};
    bar b{0};
    std::cout << a.a << ' ' << b.b;
}

打起来铛,oopsie:

steve@steve-pc /tmp> clang++ -o test.clang.O0 test.cpp
test.cpp:17:9: error: no matching constructor for initialization of 'bar'
    bar b{0};
        ^~~~
test.cpp:8:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion
      from 'int' to 'const bar' for 1st argument
struct bar {
       ^
test.cpp:8:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion
      from 'int' to 'bar' for 1st argument
struct bar {
       ^
test.cpp:13:6: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
bar::bar() = default;
     ^
1 error generated.
                                                                              [ 0s930 | Jan 27 01:35PM ]

同样的命运与G ++还有:

steve@steve-pc /tmp> g++ test.cpp
test.cpp: In function ‘int main()’:
test.cpp:17:12: error: no matching function for call to ‘bar::bar(<brace-enclosed initializer list>)’
     bar b{0};
            ^
test.cpp:8:8: note: candidate: ‘bar::bar()’
 struct bar {
        ^~~
test.cpp:8:8: note:   candidate expects 0 arguments, 1 provided
test.cpp:8:8: note: candidate: ‘constexpr bar::bar(const bar&)’
test.cpp:8:8: note:   no known conversion for argument 1 from ‘int’ to ‘const bar&’
test.cpp:8:8: note: candidate: ‘constexpr bar::bar(bar&&)’
test.cpp:8:8: note:   no known conversion for argument 1 from ‘int’ to ‘bar&&’
                                                                              [ 0s718 | Jan 27 01:35PM ]

因此,这意味着它是有效的直接初始化bar b(0),没有集合初始化。

这可能是因为,如果你不提供一个明确的构造函数实现这个可能是外部符号,例如:

bar::bar() {
  this.b = 1337; // whoa
}

编译器是不足够聪明来推断此作为一个非优化阶段无操作/内联呼叫。

© www.soinside.com 2019 - 2024. All rights reserved.