默认初始化与零初始化的区别

4

我不理解gcc 4.8.1或Visual Studio 2015在默认初始化和值初始化方面的行为。

我自己也在尝试理解这些区别,可能遇到了编译器bug?

我的问题是:有人能够解释这种行为吗?最好能告诉我应该发生什么。

我有两个类:

class Foo{
    int _bar;
public:
    void printBar(){ cout << _bar << endl; }
};

class bar{
    int ent;
public:
    int getEnt(){return ent;}
};

我将使用以下代码进行测试:
int main()
{
    Foo foo;

    foo.printBar();
    Foo().printBar();

    bar b;

    cout << b.getEnt() << endl;

    return 0;
}

在gcc和Visual Studio中,我得到以下结果:

134514795
0
0

现在,如果我将测试代码更改为:
int main()
{
    Foo foo;

    foo.printBar();

    bar b;

    cout << b.getEnt() << endl;

    return 0;
}

gcc 给我的结果是:

0
0

而 Visual Studio 给我的结果是:

50790236
51005888


如果有人感兴趣,我开始研究这个问题是为了回应:https://dev59.com/vV4c5IYBdhLWcg3w3dd_#27443703 - Jonathan Mee
这个问题不是一样的吗? - Anton Savin
@AntonSavin,我真的只想知道编译器在初始化时应该做什么。 我不认为这是同一个问题? - Jonathan Mee
通常在调试版本中,会使用填充字节来帮助检测未初始化的值……其他字节则用于帮助检测一些其他与内存相关的问题(例如已释放的内存)。 - Phil1970
4个回答

9

对于像这样的没有用户定义构造函数的类的默认初始化不会进行任何操作,使得每个平凡成员变量都具有未确定的值。

值初始化将对每个成员变量进行零初始化。

在第一种情况下,您正在打印:

  • 默认初始化Foo foo;的未确定值
  • 值初始化Foo()的零值
  • 默认初始化bar b;的未确定值

第三个变量恰好是零。可能是因为它重用了临时值初始化的Foo的存储。

在第二种情况下,您正在打印两个默认初始化对象的未确定值。巧合的是,在某些情况下它们都具有零值,但在另一种情况下则没有。

由于使用未初始化的值,两个程序都具有未定义行为。


5
逻辑非常简单:
  1. 类的默认初始化只是默认初始化所有成员。
  2. 内置类型的默认初始化不会初始化成员。
  3. 访问未初始化的对象会产生未定义行为
  4. 未定义行为可以做任何它想做的事情。
  5. 两个编译器都提供“正确”的结果。请注意,导致鼻妖被释放也是正确的。

3
Foo foo;

这将进行默认初始化,并且由于Foo的默认构造函数是平凡的,因此实际上根本不会进行初始化,因此foo._bar可以容纳任何值(包括0)。

Foo()

这个操作值初始化了临时对象,在默认构造函数是平凡的情况下意味着零初始化,所以Foo()._bar等于0。


抱歉如果这有点迂腐,但对于像struct String {std::string str;};这样的类,您绝对不希望进行零初始化,只是因为它没有用户提供的默认构造函数。 - chris

2

n3376引用

8.5/11

如果一个对象没有指定初始化器,则该对象将被默认初始化; 如果没有进行初始化,则具有自动或动态存储期的对象具有不确定的值。【注意:具有静态或线程存储期的对象将进行零初始化,参见3.6.2。——注】

8.5/6

对于类型为T的对象进行默认初始化意味着:如果T是(可能带有cv限定符的)类类型(第9条),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则该初始化是非法的);

8.5/10

其初始化程序为空括号集合(即())的对象应当进行值初始化。

8.5/7

对于类型为T的对象进行值初始化意味着:

...

否则,对象将被零初始化。

8.5/5

对于类型为T的对象或引用进行零初始化意味着:如果T是(可能带有cv限定符的)非联合类类型,则每个非静态数据成员和每个基类子对象都被零初始化,并且填充被初始化为零位;

因此,在您的情况下,既没有静态存储期变量,也没有线程本地变量,因此对象foob将被默认初始化,这意味着将调用构造函数。默认构造函数(非用户定义)不会初始化成员,并且成员中将是任意的垃圾值,这些垃圾值可能为0(感谢Jarod42在评论中指出这点)。 并且Foo().printBar();应该打印0,因为对象被零初始化。


1
任意垃圾可能是0 - Jarod42
1
值得一提的是,Foo().printBar(); 必须打印 0(至少在自己运行时如此,而不是在程序中存在其他未定义行为时)。 - chris
@Jarod42 这实际上是一个非常好的评论。我没有考虑到默认初始化可能会产生0的结果。嗯...必须有一种方法来处理这个问题,以便我可以看到发生了什么。 - Jonathan Mee

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