对象存储部分重用时,对象生命周期结束。

huangapple go评论79阅读模式
英文:

End of object lifetime if its storage is partially reused

问题

从C++20开始,根据最新的标准草案,当对象的存储部分“被重新使用”时,对象生命周期结束的规则似乎不太明确。我找到的StackOverflow上的现有答案在这个问题上并没有完全定论。

假设doubleuint64_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&lt;double*&gt;(i) because that would indeed be an aliasing violation.

huangapple
  • 本文由 发表于 2023年7月23日 23:53:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76749141.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定