未初始化的数据行为是否指定良好

Is uninitialized data behavior well specified?

本文关键字:是否 初始化 数据      更新时间:2023-10-16

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


有我能想到的最简单的课程:

class BaseClass  {
  public:
    int pub;
};

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


案例1

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

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


那么,这只是"未初始化的数据导致未指定行为"的情况,还是"引擎盖下"发生了更合乎逻辑的事情?

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

那么这只是"未初始化的数据导致未指定行为"的情况吗?

是的。。。

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

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

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

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

通常,堆或堆栈上的任何 POD 都将未初始化,除非您自己初始化它,并且该值将是未定义的,当您重新编译或再次运行程序时可能会更改。 通常,任何全局 POD 都将初始化为零,除非您将其初始化为其他内容。

检测未初始化

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

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

// 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.
}

我使用术语"零初始化"/"默认初始化",但技术上稍微复杂一些。"默认初始化"将变为pub成员的"无初始化"。而()调用"值初始化",该值初始化成为pub成员的"零初始化"。

注意:由于BaseClass是一个 POD,因此此类的行为与内置类型一样。如果将 BaseClass 交换为任何标准类型,则行为是相同的。

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

没有任何初始值设定项的 POD 对象将不会按默认值初始化。他们只是包含垃圾。

从标准 8.5 初始值设定项,

"如果没有为对象指定初始值设定项,并且该对象是 (可能符合 cv 标准(非 POD 类类型(或其数组(,以及 对象应默认初始化;如果对象是 符合 CONST 条件的类型,底层类类型应具有 用户声明的默认构造函数。否则,如果没有初始值设定项 为非静态对象指定,该对象及其子对象,如果 任何,具有不确定的初始值;如果对象或任何 它的子对象是常量限定类型,程序是 畸形。

您可以像这样将 POD 结构的所有成员清零初始化,

BaseClass object={0};

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

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

那将是一个更好的做法。

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

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

案例 1

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

案例2

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

案例3

相同。

你似乎期待对未初始化对象的"价值"有一些保证,同时承认不存在这样的价值。