在放置新数据之前初始化数据是未定义的行为吗


struct A { //POD class
char data[10];
void print() {std::cout << data;}
};
int main() {
char buffer[11] = "HELLO"; //sets values in buffer
A* a = new(buffer)A;
a->print(); // read from memory buffer
a->~A();
}

从类的角度来看,这是从未初始化的内存中读取的,但从内存的角度看,内存实际上已经初始化。这种行为是不明确的,还是仅仅是危险的?

[注意:对于那些对new(buffer)Aa->~A()感兴趣的人来说,这被称为"placement new",用于在内存中的特定缓冲区中构造对象。这是vector在其内部缓冲区中隐藏构造类的做法]

即使在这种特殊情况下类成员和数组保证具有相同的地址(基于〔basic.compound〕/4.3,加上新表达式的要求实际上不允许编译器做任何其他事情,只允许将该对象放在缓冲区的开头(,我敢肯定这是未定义的行为。

我相信该标准的相关部分将是基本的.memobj§1:

当获得具有自动或动态存储持续时间的对象的存储时,该对象具有不确定的值,如果没有对该对象执行初始化,则该对象将保留不确定值,直到该值被替换为止([expr.ass](。

我不知道标准中有任何其他措辞,在任何情况下都会根据对象生命周期开始前创建的存储中的内容来保证对象的初始值。您的对象是默认初始化的,属于类类型,因此,将调用默认构造函数。构造函数中的成员没有mem初始值设定项,类中也没有默认初始值设定器,因此,该成员将被默认初始化[class.base.init/9.3]。由于数组的元素是基本类型的,因此不会对它们执行初始化。这意味着basic.indet§2适用于数组内容的任何使用(这将发生在operator <<内部(

如果评估产生了不确定的值,则行为是未定义的,除非在以下情况下[…]

由于您的案例与列为异常的任何案例都不匹配,因此您的程序应该具有未定义的行为…

构造对象后缓冲区的状态是未定义的。在构造A期间,编译器可以自由地将"format c;:"写入其中。他们还可以自由地优化缓冲区的预加载,只需丢弃您在该行中所做的操作。

您的print代码就是UB,因为<<需要一个nul终止的缓冲区。

尽管编译器在构建对象之前将新的放置解释为"导入"存储中的任何位模式通常不会花费任何成本,但也有一些难以描述的角落情况,这样做可能会使优化变得非常复杂或阻碍优化,而不会带来任何实际好处。因此,该标准允许编译器在不需要任何成本或对其客户有利的情况下导入位模式,而不要求编译器在成本高昂但对其客户没有好处的情况下这样做。

虽然如果有一种新的放置语法形式可以明确指定应该导入位模式,这会很有帮助,但在编写标准时,这并不是必要的。在大多数情况下,导入位模式是有用的,它不会花费任何费用,编译器也会这样做,无论是否强制执行。在无用的情况下,编译器是否支持该行为并不重要。这种语法会导致编译器做一些有用的事情,而这些事情在其他情况下是不会做的,这种情况非常罕见,因此不需要适应它们。

显然,自从placementnew首次标准化以来的几十年里,编译器的理念已经发生了变化,逐位导入很有用,但编译器不能可靠地支持它的情况要普遍得多。一个明智的解决方案是添加两种新的语法形式——一种要求编译器导入位模式,另一种明确声明位模式无关紧要——因为程序员可能比编译器编写者更了解位模式是否重要。然而,到目前为止,这种情况还没有发生,这使得像您这样的构造处于一种尴尬的状态,即某些实现有效地支持它们,而其他实现则不支持,并且没有任何好的方法来识别支持它们的实现。

最新更新