【问题标题】:Is uninitialized data behavior well specified?未初始化的数据行为是否明确指定?
【发布时间】:2011-07-26 02:09:48
【问题描述】:

注意:我使用的是 g++ 编译器(我听说它非常好,应该非常接近标准)。


我有我能想到的最简单的类:

class BaseClass  {
  public:
    int pub;
};

然后我有三个同样简单的程序来创建 BaseClass 对象并打印出它们数据的 [未初始化] 值。


案例一

BaseClass B1;
cout<<"B1.pub = "<<B1.pub<<endl;

打印出来:

B1.pub = 1629556548

这很好。我实际上认为它会被初始化为零,因为它是 POD 或普通旧数据类型或类似的东西,但我猜不是?到目前为止一切顺利。


案例2

BaseClass B1;
cout<<"B1.pub = "<<B1.pub<<endl;
BaseClass B2;
cout<<"B2.pub = "<<B2.pub<<endl;

打印出来:

B1.pub = 1629556548
B2.pub = 0

这绝对是奇怪的。我以完全相同的方式创建了两个相同的对象。一个被初始化,另一个没有。


案例3

BaseClass B1;
cout<<"B1.pub = "<<B1.pub<<endl;
BaseClass B2;
cout<<"B2.pub = "<<B2.pub<<endl;
BaseClass* pB3 = new BaseClass;
cout<<"B3.pub = "<<pB3->pub<<endl;

打印出来:

B1.pub = 0
B2.pub = 0
B3.pub = 0

这是迄今为止最奇怪的。它们都被初始化为零。我所做的只是添加两行代码,它改变了以前的行为。


这只是“未初始化的数据导致未指定的行为”的情况,还是“幕后”发生了更合乎逻辑的事情?

我真的很想了解默认的构造函数/析构函数行为,因为我觉得这对于完全理解继承的东西非常重要..

【问题讨论】:

  • 在 C++ 中读取这些未初始化的对象实际上是未定义的行为

标签: c++ initialization


【解决方案1】:

所以这只是“未初始化的数据导致未指定的行为”的情况

是的……

有时,如果您调用malloc(或new,后者调用malloc),您将获得用零填充的数据,因为它位于内核的新页面中。其他时候它会充满垃圾。如果你把东西放在堆栈上(即自动存储),你几乎肯定会得到垃圾——但它可能很难调试,因为在你的系统上,垃圾可能碰巧在某种程度上是可以预测的。对于堆栈上的对象,您会发现在完全不同的源文件中更改代码可以更改您在未初始化的数据结构中看到的值。

关于 POD: 某物是否是 POD 在这里真的是一个红鲱鱼。我只是解释它,因为问题提到了 POD,并且谈话从那里脱轨。两个相关概念是存储持续时间和构造函数。 POD 对象没有构造函数,但不是所有没有构造函数的都是 POD。 (从技术上讲,POD 对象没有 non-trivial 构造函数,也没有具有 non-trivial 构造函数的成员。)

存储时长:共有三种。 静态持续时间用于全局变量,自动用于局部变量,动态用于堆上的对象。 (这是一种简化,并不完全正确,但如果您需要完全正确的内容,您可以自己阅读 C++ 标准。)

任何具有静态存储持续时间的东西都会被初始化为零。所以如果你创建一个BaseClass 的全局实例,那么它的pub 成员将为零(起初)。由于您将它放在堆栈和堆上,因此该规则不适用——并且您无需执行任何其他操作来初始化它,因此它未初始化。它恰好包含最后一段代码在内存中使用它时留下的任何垃圾。

通常,除非您自己初始化,否则堆或堆栈上的任何 POD 都将未初始化,并且值将未定义,可能会在您重新编译或再次运行程序时发生变化。通常,除非您将其初始化为其他值,否则任何全局 POD 都将被初始化为零。

检测未初始化值:尝试使用 Valgrind 的 memcheck 工具,它会帮助您找到使用未初始化值的位置 — 这些通常是错误。

【讨论】:

  • BaseClass 在我看来就像一个 POD。那里没有用户定义的ctor。并且具有静态存储持续时间的 所有 对象在 可能 值初始化/构造之前进行零初始化。
  • 这不是“为什么”。这是真的,但不是“为什么”。 int 成员未初始化是因为它是内置类型;这与 POD 不同。而BaseClass 是 POD 真的 与它无关。
  • 不好意思,是不是因为BaseClass是POD,所以放在栈上它的成员一定是未初始化的?我不确定我是否理解您要说的内容,而且我对标准不那么熟悉...
  • 不,这根本不是事实。对于任何类类型,任何未在 ctor 成员初始化器中显式初始化的非静态数据成员都是默认构造的,除非它是内置类型(intcharbool 和朋友)在这种情况下它是未初始化的。封装类类型是否为 POD 与如何分配实例无关。
  • Tomalak:我真诚地试图理解你在说什么。你能举一个 POD 的例子,当它在没有初始化的情况下放在堆栈上时,初始化它的成员?因为我想说的是它不会发生。也许这在聊天中更合适?
【解决方案2】:

这取决于你如何声明它们:

// Assuming the POD type's
class BaseClass
{
  public:
    int pub;
};

静态存储持续时间对象

这些对象总是零初始化。

// Static storage duration objects:
// PODS are zero initialized.

BaseClass    global; // Zero initialized pub = 0

void plop()
{
    static BaseClass functionStatic;   // Zero initialized.
}

自动/动态存储持续时间对象

这些对象可能默认初始化或零初始化,具体取决于您声明它们的方式

void plop1()
{
    // Dynamic
    BaseClass*  dynaObj1   = new BaseClass;   // Default initialized (does nothing)
    BaseClass*  dynaObj2   = new BaseClass(); // Zero Initialized

    // Automatic
    BaseClass   autoObj1;                     // Default initialized (does nothing)
    BaseClass   autoObj2   =     BaseClass(); // Zero Initialized

    // Notice that zero initialization of an automatic object is not the same
    // as the zero initialization of a dynamic object this is because of the most
    // vexing parse problem

    BaseClass    autoObj3(); // Unfortunately not a zero initialized object.
                             // Its a forward declaration of a function.
}

我使用术语“零初始化”/“默认初始化”,但在技术上稍微复杂一些。 'Default-Initialization' 将成为pub 成员的'no-initialization'。而() 调用成为pub 成员的“零初始化”的“值初始化”。

注意:由于BaseClass 是一个 POD,所以这个类的行为就像内置类型一样。如果您将 BaseClass 换成任何标准类型,则行为是相同的。

【讨论】:

  • 谢谢,这是我的另一个问题: BaseClass pB = new BaseClass; 有什么区别和 BaseClass pB = new BaseClass(); ?它们都编译并导致案例 3 行为。
【解决方案3】:

在所有三种情况下,这些 POD 对象都可能具有不确定的值。

没有任何初始化程序的 POD 对象将不会按默认值初始化。它们只是包含垃圾。

来自标准 8.5 初始化程序,

"如果没有为一个对象指定初始化器,并且该对象属于 (可能是 cv 限定的)非 POD 类类型(或其数组), 对象应默认初始化;如果对象是 const 限定类型,基础类类型应具有 用户声明的默认构造函数。 否则,如果没有初始化器 为非静态对象、对象及其子对象指定,如果 any,有一个不确定的初始值;如果对象或任何 它的子对象是 const 限定类型,程序是 格式不正确。”

您可以像这样对 POD 结构的所有成员进行零初始化,

BaseClass object={0};

【讨论】:

    【解决方案4】:

    按照您编写类的方式,pub 的值是未定义的,可以是任何值。如果您创建一个默认构造函数,它将调用pub 的默认构造函数 - 它将保证为零:

    class BaseClass  {
      public:
        BaseClass() : pub() {}; // calling pub() guarantees it to be zero.
        int pub;
    };
    

    这将是一个更好的做法。

    【讨论】:

    • 这很好。所以你是说如果我用一个显式的(虽然也是非常默认的)构造函数覆盖默认构造函数,那么我总是会初始化我的基本数据类型?那是标准的一部分吗?
    • @Jimmy:你会用 user-defined 默认构造函数覆盖 synthesised 默认构造函数。您不会自动初始化它们:请注意 @littleadv 在其 ctor 的成员初始化程序中明确初始化了 pub
    • 您完美解读并回答了我的评论。
    【解决方案5】:

    一般来说,是的,未初始化的数据会导致未指定的行为。这就是 C# 等其他语言采取措施确保您不使用未初始化数据的原因。

    【讨论】:

    【解决方案6】:

    这就是为什么你总是、总是、总是将一个类(或任何变量)初始化为稳定状态,而不是依赖编译器为你做这件事;特别是因为一些编译器故意用垃圾填充它们。事实上,唯一没有填充垃圾的 POD MSVC++ 是bool,它们被初始化为true。有人会认为将其初始化为 false 会更安全,但那是微软为你准备的。

    【讨论】:

    • 调试通过 MSVC++ 构建并填充 0xDEADBEEF (IIRC),这是此表示中的“可识别”值。不仅仅是bool。这个,转换为bool,是true。用0 填充不会很有用;如果您错过了某处的非初始化,确实会产生危险的误导,但认为您的积分数据无论如何都保证以0 的值开始生命。
    【解决方案7】:

    案例一

    无论封装类型是否为 POD,内置类型的数据成员都不会被封装默认构造函数默认初始化。

    案例2

    不,两者都没有初始化。其中一个的内存位置的底层字节恰好0

    案例3

    一样。

    您似乎期望对未初始化对象的“价值”有一些保证,同时声称不存在这样的价值。

    【讨论】:

    • 抱有不切实际的期望是我的天性,但当它们被证明是错误的时,我从不特别沮丧。我知道不初始化是不好的做法,但想学习所有可用的 C++ 快捷方式。
    • @Jimmy:这是我最感兴趣的矛盾。:)
    猜你喜欢
    • 2018-11-05
    • 1970-01-01
    • 1970-01-01
    • 2020-07-18
    • 1970-01-01
    • 2018-09-16
    • 2014-12-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多