英文:
End of object lifetime if its storage is partially reused
问题
从C++20开始,根据最新的标准草案,当对象的存储部分“被重新使用”时,对象生命周期结束的规则似乎不太明确。我找到的StackOverflow上的现有答案在这个问题上并没有完全定论。
假设double和uint64_t的对齐方式和存储空间完全相同,我从下面的代码示例中得到以下问题:
在下面的代码中,new (&a->c1) uint64_t{4}
是否会结束对象a的生命周期?
struct A
{
double d;
int i;
double c1;
double d1;
double d2;
};
int main () {
A* a = new A{};
uint64_t* i = new (&a->c1) uint64_t{4}; //这会结束a的生命周期吗?这显然会结束a.c1的生命周期
*i = 124; //由于严格别名规则,这必须是未定义行为
return 0;
}
标准明确规定了对象生命周期结束的规则:
http://eel.is/c++draft/basic.life#1.3
如果T是非类类型,对象将被销毁,或者
如果T是类类型,析构函数调用开始,或者
对象所占用的存储被释放,或者被一个不嵌套在o内的对象重新使用([intro.object])。
然后,标准正式定义了嵌套对象的条件:
如果对象a嵌套在另一个对象b内,那么:(4.1) a是b的子对象,或者(4.2) b为a提供存储,或者(4.3)存在对象c,其中a嵌套在c内,而c嵌套在b内。
对于我们的代码示例,我们受限于以下条件:
a是b的子对象,或者(4.2) b为a提供存储,或者(4.3)存在对象c,其中a嵌套在c内,而c嵌套在b内。
然后,标准定义了什么是提供存储:
如果在与另一个类型为“N个无符号字符的数组”或“N个std::byte的数组”([cstddef.syn])关联的存储中创建了一个完整对象([expr.new]),如果满足以下条件,则该数组为创建的对象提供存储:(3.1) e的生命周期已经开始但尚未结束,以及(3.2) 新对象的存储完全适合e内,以及(3.3)没有满足这些约束的数组对象嵌套在e内。
这意味着只有std::byte或unsigned char数组可以提供存储...因此,我推断在a的存储内创建**uint64_t{4}对象会结束对象a的生命周期,因为uint64_t{4}**不嵌套在a内...不过,我对这个结论并不是特别确定。
英文:
Starting from C++20 and as of the latest standard draft, it appears like the rules for the end of an object's lifetime are not super clear when the object's storage is partially "re-used". The existing answers on StackOverflow that I've found are not fully conclusive on this matter.
Given the assumption that double and uint64_t alignment and storage space would be exactly the same, I've got the following question from an code example below:
will new (&a->c1) uint64_t{4}
below end the lifetime of object a?
struct A
{
double d;
int i;
double c1;
double d1;
double d2;
};
int main () {
A* a = new A{};
uint64_t* i = new (&a->c1) uint64_t{4}; //will this end lifetime of a? It would clearly end the lifetime of a.c1
*i = 124; //this must be UB because of strict-aliasing rules
return 0;
}
The standard clearly stipulates the rules at the end of an object's lifetime:
http://eel.is/c++draft/basic.life#1.3
> - if T is a non-class type, the object is destroyed, or
>
> - if T is a class type, the destructor call starts, or
>
> - the storage which the object occupies is released, or is reused by an object that is not nested within o ([intro.object]).
Then formally the object is nested in the following conditions:
> An object a is nested within another object b if: (4.1) a is a
> subobject of b, or (4.2) b provides storage for a, or (4.3) there
> exists an object c where a is nested within c, and c is nested within
> b.
For our code example we are bound to
> subobject of b, or (4.2) b provides storage for a, or (4.3) there
Then the standard defines what provides storage means
> If a complete object is created ([expr.new]) in storage associated
> with another object e of type “array of N unsigned char” or of type
> “array of N std::byte” ([cstddef.syn]), that array provides storage
> for the created object if: (3.1) the lifetime of e has begun and not
> ended, and (3.2) the storage for the new object fits entirely within
> e, and (3.3) there is no array object that satisfies these constraints
> nested within e.
This implies that only arrays of std::byte or unsigned char can provide storage...From which I imply that creating uint64_t{4} object inside a's storage would end the lifetime of object a, as uint64_t{4} is not nested in a...Although I'm not really sure about this conclusion.
答案1
得分: 3
以下是要翻译的内容:
"will new (&a->c1) uint64_t{4} below end the lifetime of object a?"
这不会结束与变量 a
关联的对象的生命周期,但会结束 a
指向的对象的生命周期,即 A
对象的生命周期,而不是 A*
对象。是的,由于新表达式重用了(部分)其存储空间,因此 A
对象的生命周期将在其中重用其存储空间的对象之后结束,而这个对象不嵌套在它内部。A
对象的 c1
子对象也将结束其生命周期,因为新对象也重用了它的存储空间,而没有嵌套在其中。我还期望这将结束 A
对象的所有其他子对象的生命周期,但目前标准似乎没有明确规定。
*i = 124; //this must be UB because of strict-aliasing rules
这是良好定义的。i
指向你在前面的新表达式中创建的 uint64_t
类型的对象。因此,这不会违反别名规则。
在新表达式之后,你不能再使用 a
指向的对象。特别是你不能再访问 a.c1
,因为该(子)对象的生命周期也已结束,你也不能通过 reinterpret_cast<double*>(i)
访问相同的内存位置,因为那将确实违反别名规则。
英文:
> will new (&a->c1) uint64_t{4} below end the lifetime of object a?
Not of the object associated with the variable a
, but the object that a
points to, i.e. the A
object, not the A*
object. Yes, the A
object's lifetime will end with reuse of (part of) its storage in the new-expression because the object reusing its storage isn't nested within it. The c1
subobject of the A
object will also have its lifetime ended since the new object reuses its storage as well without being nested in it. I would also expect that this would end the lifetime of all other subobjects of the A
object, however currently the standard doesn't seem to say that.
*i = 124; //this must be UB because of strict-aliasing rules
That's well-defined. i
is pointing to an object of type uint64_t
that you created in the previous new-expression. So it can't be an aliasing violation.
After the new-expression you can't use the object that a
points to any more. In particular you can't access a.c1
any more, because that (sub)object's lifetime has also ended and you can't access the same memory location through reinterpret_cast<double*>(i)
because that would indeed be an aliasing violation.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论