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

时间:2018-12-12 17:41:14

标签: c++ language-lawyer undefined-behavior object-lifetime placement-new

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)A and a->~A()感到好奇的人,这称为“新放置”,用于在内存中的特定缓冲区中构造对象。这是vector进行的内部构造,以在其内部缓冲区中构造类]

3 个答案:

答案 0 :(得分:8)

即使在这种特殊情况下,也保证了类成员和数组具有相同的地址(基于[basic.compound]/4.3以及requirements on new-expressions实际上不允许编译器执行任何操作的事实)除了将那个对象放在缓冲区的开头之外),我很确定这是未定义的行为。

我相信该标准的相关部分为basic.memobj §1

  

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

我不知道标准中任何地方的措辞,无论如何都无法根据对象在其生命周期开始之前在其中创建的存储内部内容来保证有关对象的初始值。您的对象是默认初始化的并且是类类型的,因此,将调用默认构造函数。构造函数中的成员没有 mem-initializer ,该类中也没有默认的初始化器,因此,该成员将被默认初始化[class.base.init/9.3]。由于数组的元素是基本类型,no initialization will be performed for them。这意味着basic.indet §2适用于对数组内容的任何使用(发生在operator <<内部)

  

如果评估产生不确定的值,则在以下情况下行为是不确定的[...]

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

答案 1 :(得分:2)

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

然后,您的print代码是UB,因为<<需要一个以nul结尾的缓冲区。

答案 2 :(得分:1)

尽管对于编译器来说,在构造对象之前,将新的位置解释为“导入”存储中发生的任何位模式通常不会花费任何费用,但是在某些情况下,很难做到这一点极大地复杂化或阻碍了优化,而没有提供任何实际的好处。因此,该标准允许编译器在不花费任何费用或不会使他们的客户受益的情况下导入位模式,而在要求高昂而又没有使他们的客户受益的情况下则不需要他们这样做。

尽管如果有一种形式的放置新语法可以明确地指定应导入位模式,这将是有帮助的,但是在编写标准时,这种事情就不必要了。在大多数情况下,导入位模式会很有用,它将不花任何费用,并且无论是否强制要求,编译器都会这样做。在没有用的情况下,编译器是否支持该行为无关紧要。这种语法会导致编译器做一些本来不会做的有用的事情,这种情况很少发生,以至于不需要容纳它们。

很显然,自从首次将new放置标准化以来的几十年中,编译器理念发生了变化,并且按位导入会很有用,但编译器无法可靠地支持它的情况变得更加普遍。一个明智的解决方案是在程序员可能会知道更多的基础上,添加两种新的语法形式-一种将要求编译器导入位模式,而另一种则将显式声明该位模式无关紧要。关于位模式是否比编译器编写者可能重要的问题。但是,到目前为止,这还没有发生,使像您这样的构造处于尴尬的状态,无法得到某些实现的有用支持,而有些实现却无法得到其他实现的有效支持。