生命周期结束规则有何不同?

时间:2018-12-14 19:26:02

标签: c++ language-lawyer undefined-behavior

说明部分中的

https://en.cppreference.com/w/cpp/language/lifetime包含此代码,在此处复制:

struct A {
  int* p;
  ~A() { std::cout << *p; } // if n outlives a, prints 123
};
void f() {
  A a;
  int n = 123; // if n does not outlive a, this is optimized out (dead store)
  a.p = &n;
}

在此注释部分中要说什么?

据我了解,代码是UB(或者是UB),因为很明显n不会超过a

这是什么意思:

  

非类对象之间的生存期结束规则之间的差异(结束   存储时间)和类对象(相反的构造顺序)   重要

但这并不重要如何

整个章节让我很困惑。

2 个答案:

答案 0 :(得分:27)

这是C ++生命周期规则的一个奇怪方面。 [basic.life]/1 tells us对象的生存期结束:

  
      
  • 如果T 是具有非平凡析构函数的类类型([class.dtor]),则析构函数调用开始,或者
  •   
  • 对象占用的存储空间已释放,或被未嵌套在o中的对象([intro.object])重用。
  •   

已添加重点。 int不是“带有非平凡析构函数的类类型”,因此其生存期仅在释放其占用的存储空间时结束。相比之下,A是具有非平凡析构函数的类类型”,因此其寿命在析构函数被调用时结束。

根据[basic.stc.auto]/1,在退出作用域时释放作用域的存储空间:

  

[具有自动存储期限的变量]的存储将持续到创建它们的块退出为止。

但是自动变量按照[stmt.jump]/2销毁了:

  

在退出合并范围时(无论如何完成),在该合并范围中构造的具有自动存储期限的对象将按照其构造的相反顺序被破坏。

注意,已指定破坏顺序,但未指定自动存储释放顺序。这意味着该实现可以在销毁每个变量后立即释放存储,或稍后或以任意其他顺序一次释放所有存储。

现在,它使用单数形式进行存储(“用于...的存储”)而不是单独讨论每个变量的事实可能表明,意图是将整个存储整体立即释放该范围。但是标准中对此没有明确说明。因此,只要在释放存储之前销毁变量,销毁与释放的任何顺序似乎都是合法的。

这意味着代码完全有可能运行,n的生存期可能超过a。但是尚不确定它是否有效。

答案 1 :(得分:10)

此示例是从Core Language Issue 2256借来的:

  

部分::6.8 [basic.life] 状态:起草提交者: Richard Smith 日期: 2016- 03-30

     

根据6.4 [basic.lookup]项目符号1.4,以下示例定义了行为,因为n的生存期一直延长到释放存储为止,这是在a的析构函数运行之后:< / p>

  void f() { 
    struct A { int *p; ~A() { *p = 0; } } a; 
    int n; 
    a.p = &n; 
  } 
     

如果所有对象的生存期结束,无论它们是否具有非平凡的析构函数,都将被视为相同,则会更加一致。

     

2018年3月会议的笔记:

     

CWG同意建议的方向。

关键思想是,对象的生存期是在其销毁时还是在其内存释放时结束,可能会影响程序的语义。在示例中,

  1. 如果n的生存期在n销毁时结束,则该程序未定义;

  2. 如果n的生存期在释放内存之前结束,则程序已定义行为 1

因此,需要进一步讨论以确定对象的生存时间何时结束。


1 这是因为Core Language Issue 2115

  

部分::9.6 [stmt.jump] 状态:起草提交者: Richard Smith 日期: 2015- 04-16

     

标准未指定在退出块时自动变量销毁与释放变量存储之间的相对顺序:是先执行所有析构函数然后释放存储,还是将它们交错? >      

2016年2月会议的笔记:

     

CWG同意,存储应该一直持续到所有销毁完成为止,尽管“按原样”规则将允许对该顺序进行不可观察的优化。

目的是自动变量的内存释放在所有销毁完成之后发生。