C + + 0初始化-为什么这个程序中的‘ b’是未初始化的,而‘ a’是初始化的?

根据公认的(也是唯一的) 这个堆栈溢出问题答案,

定义构造函数

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 类型,默认的初始化是零初始化。

根据 这个问题的公认答案,

  1. 如果 POD 成员没有在构造函数中初始化,也没有通过 C + + 11初始化 在类内初始化时,它是默认初始化的。

  2. 不管堆栈还是堆,答案都是一样的。

  3. 在 C + + 98中(而不是之后) ,new int ()被指定为执行 零初始化

尽管试图把我的头(尽管是 很小)包裹在 默认构造函数默认初始化之中,我还是想不出一个解释。

9818 次浏览

行为上的差异来自于这样一个事实,根据 [dcl.fct.def.default]/5bar::bar用户提供,而 foo::foo不是 1。因此,foo::fooValue-initialize 值初始化它的成员(意思是: bar::bar0 foo::a) ,但是 bar::bar将保持未初始化的 bar::bar1。


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

如果函数是 用户声明的且在第一次声明时没有显式默认或删除,则该函数是用户提供的。

2)

来自 [ dcl.init # 6]:

初始化 T 类型的对象意味着:

  • 如果 t 是一个(可能是 cv 限定的)类类型,要么没有缺省构造函数([ class.ctor ]) ,要么是一个用户提供或删除的缺省构造函数,那么对象是默认初始化的;

  • 如果 T 是 (可能是 cv 限定的) 没有用户提供或删除缺省构造函数的类类型,那么对象就是零初始化的,并且检查默认初始化的语义约束,如果 T 有一个非平凡的缺省构造函数,则对象被默认初始化;

来自 [ dcl.init.list ]:

类型 T 的对象或引用的 List 初始化定义如下:

  • 否则,如果初始化器列表中没有元素,而 t 是一个带有缺省构造函数的类类型,那么对象就是值初始化的。

来自 维托里奥 · 罗密欧的回答

这里的问题很微妙,你可能会这么想

bar::bar() = default;

会给你一个编译器生成的缺省构造函数,它确实这样做了,但是现在它被认为是用户提供的。[ dcl.fct.def.default ]/5指出:

显式默认函数和隐式声明函数统称为默认函数,实现应该为它们提供隐式定义([ class.ctor ][ class.dtor ] ,[ class.copy.ctor ] ,[ class.copy.sign ]) ,这可能意味着将它们定义为删除。如果函数是用户声明的,并且在第一次声明时没有显式默认或删除,则该函数是用户提供的。一个用户提供的显式默认函数(即,在第一次声明之后显式默认)定义在它显式默认的位置; 如果这样一个函数被隐式定义为删除,程序就是病态的。[注意: 在第一次声明后将函数声明为默认函数可以提供高效的执行和简洁的定义,同时为不断发展的代码库提供稳定的二进制接口。ー尾注]

强调我的

因此,我们可以看到,由于您在第一次声明 bar()时没有默认 bar(),所以现在认为它是用户提供的。因为那个 [ dcl.init ]/8.2

如果 T 是一个没有用户提供或删除缺省构造函数的类型(可能是 cv 限定的) ,那么对象是零初始化的,并且检查默认初始化的语义约束,如果 T 有一个非平凡的缺省构造函数,那么对象是默认初始化的;

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

如果 T 是一个(可能是 cv 限定的)类类型([ class ]) ,要么没有缺省构造函数([ class.default.ctor ]) ,要么是一个用户提供或删除的缺省构造函数,那么对象是默认初始化的;

来自 首选:

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

聚合是下列类型之一:

[剪]

  • 类类型[剪切] ,它具有

    • (不同的标准版本有不同的版本)

    • 没有用户提供的、继承的或显式的构造函数(允许显式默认或删除的构造函数)

    • [剪辑](有更多的规则,适用于两个类)

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

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

总体初始化的影响如下:

  • [剪辑](一些与本案无关的细节)

  • 如果初始值设定项子句的数目小于成员的数目或初始值设定项列表完全为空,则剩余的 成员是值初始化的

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

另一方面,对于 barT object {};是值初始化(类实例的值初始化,而不是成员的值初始化!).因为它是一个带有缺省构造函数的类类型,所以缺省构造函数被称为。您定义的默认缺省构造函数初始化成员(由于没有成员初始化器) ,在 int(非静态存储)的情况下,这使得 b.b具有不确定的值。

对于 pod 类型,默认初始化为零初始化。

不,这样不对。


附言。关于你的实验和结论: 看到输出为零并不一定意味着变量初始化为零。对于垃圾值来说,零是完全可能的数字。

为此,我在发布之前大概运行了5 ~ 6次程序,现在大约运行了10次,a 总是为零。有点变化。

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

我还尝试了 set (CMAKE _ CXX _ STANDARD 14) ,结果是一样的。

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

我怎样才能稍微动一下我的 RAM,如果那里是零,那么现在应该是别的什么东西

在 C + + 中没有保证使未初始化的值显示为非零的方法。

知道变量被初始化的唯一方法是将程序与语言的规则进行比较,并验证规则是否说明变量被初始化了。在这种情况下,a.a确实被初始化了。

我试着运行你提供的 test.cpp代码片段,通过 gcc & clang 和多个优化级别:

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 ]

这就是有趣的地方,它清楚地显示了 clang O0 build 正在读取随机数,大概是堆栈空间。

我迅速打开我的 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;
}

叮当响:

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
}

编译器不够聪明,无法在未优化阶段将其推断为 no-op/an inline 调用。