在放置新数据之前初始化数据是未定义的行为吗?
Is it undefined behavior to initialize data before 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();
}
从class'的角度来看,这是对未初始化内存的读取,但从内存的角度来看,内存实际上已经被初始化了。这是未定义的行为,还是仅仅是危险的?
[注意:对于new(buffer)A
and a->~A()
好奇的人,这叫做"placement new",用于在内存中的特定缓冲区中构造对象。这是 vector
在内部缓冲区中构造 classes]
即使 class 成员和数组在这种特殊情况下保证具有相同的地址(基于 [basic.compound]/4.3 plus the fact that the requirements on new-expressions 实际上不允许编译器做任何其他事情对象就在缓冲区的开头),我很确定这是未定义的行为。
我相信标准的相关部分是 basic.memobj §1:
When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced ([expr.ass]).
我不知道标准中有任何额外的措辞在任何情况下都会根据对象在其生命周期开始之前创建的存储中的内容来保证对象的初始值。您的对象是默认初始化的并且是 class 类型,因此,将调用默认构造函数。构造函数中的成员没有 mem-initializer,class 中也没有默认初始化程序,因此,成员将被默认初始化 [class.base.init/9.3]. Since the elements of the array are of fundamental type, no initialization will be performed for them. That means that basic.indet §2适用于对数组内容的任何使用(这将发生在 operator <<
)
If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases […]
由于您的案例不匹配任何列为异常的案例,您的程序应该有未定义的行为......
构造对象后缓冲区的状态未定义。在 A
的构造过程中,编译器可以自由地将 "format c;:"
写入其中。他们也可以免费优化您对缓冲区的预加载,只需丢弃您在该行所做的操作。
您的 print
代码是 UB,因为 <<
需要一个以 nul 结尾的缓冲区。
尽管编译器通常不会花费任何成本将新的位置解释为 "importing" 在构造对象之前存储中恰好有任何位模式,但仍有一些难以表征的极端情况这样做可能会大大复杂化或阻碍优化,而不会提供任何实际好处。因此,该标准允许编译器在不花费任何成本或对客户有利的情况下导入位模式,而不要求他们在成本高昂且对客户无益的情况下导入位模式。
虽然如果有一种放置新语法的形式明确指定应导入位模式会很有帮助,但在编写标准时并不认为有必要这样做。在大多数情况下,导入位模式是有用的,它不会花费任何成本,而且无论强制与否,编译器都会这样做。在它无用的情况下,编译器是否支持该行为并不重要。这种语法会导致编译器做一些他们不会做的有用的事情的情况非常罕见,因此没有必要容纳它们。
显然,自 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();
}
从class'的角度来看,这是对未初始化内存的读取,但从内存的角度来看,内存实际上已经被初始化了。这是未定义的行为,还是仅仅是危险的?
[注意:对于new(buffer)A
and a->~A()
好奇的人,这叫做"placement new",用于在内存中的特定缓冲区中构造对象。这是 vector
在内部缓冲区中构造 classes]
即使 class 成员和数组在这种特殊情况下保证具有相同的地址(基于 [basic.compound]/4.3 plus the fact that the requirements on new-expressions 实际上不允许编译器做任何其他事情对象就在缓冲区的开头),我很确定这是未定义的行为。
我相信标准的相关部分是 basic.memobj §1:
When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced ([expr.ass]).
我不知道标准中有任何额外的措辞在任何情况下都会根据对象在其生命周期开始之前创建的存储中的内容来保证对象的初始值。您的对象是默认初始化的并且是 class 类型,因此,将调用默认构造函数。构造函数中的成员没有 mem-initializer,class 中也没有默认初始化程序,因此,成员将被默认初始化 [class.base.init/9.3]. Since the elements of the array are of fundamental type, no initialization will be performed for them. That means that basic.indet §2适用于对数组内容的任何使用(这将发生在 operator <<
)
If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases […]
由于您的案例不匹配任何列为异常的案例,您的程序应该有未定义的行为......
构造对象后缓冲区的状态未定义。在 A
的构造过程中,编译器可以自由地将 "format c;:"
写入其中。他们也可以免费优化您对缓冲区的预加载,只需丢弃您在该行所做的操作。
您的 print
代码是 UB,因为 <<
需要一个以 nul 结尾的缓冲区。
尽管编译器通常不会花费任何成本将新的位置解释为 "importing" 在构造对象之前存储中恰好有任何位模式,但仍有一些难以表征的极端情况这样做可能会大大复杂化或阻碍优化,而不会提供任何实际好处。因此,该标准允许编译器在不花费任何成本或对客户有利的情况下导入位模式,而不要求他们在成本高昂且对客户无益的情况下导入位模式。
虽然如果有一种放置新语法的形式明确指定应导入位模式会很有帮助,但在编写标准时并不认为有必要这样做。在大多数情况下,导入位模式是有用的,它不会花费任何成本,而且无论强制与否,编译器都会这样做。在它无用的情况下,编译器是否支持该行为并不重要。这种语法会导致编译器做一些他们不会做的有用的事情的情况非常罕见,因此没有必要容纳它们。
显然,自 placement new 首次标准化以来的几十年里,编译器理念发生了变化,按位导入很有用但编译器不能可靠地支持它的情况更为普遍。一个明智的解决方案是添加两种新的句法形式——一种要求编译器导入位模式,另一种明确声明位模式无关紧要——基于程序员可能了解更多关于位模式是否比编译器编写者可能更重要。然而,到目前为止,这还没有发生,让像您这样的构造处于一种尴尬的状态,即得到一些实现的有效支持而不是其他实现,没有任何好的方法来识别支持它们的实现。