加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c – 琐碎的析构函数会导致混叠

发布时间:2020-12-16 05:05:04 所属栏目:百科 来源:网络整理
导读:C11§3.8.1声明,对于具有普通析构函数的对象,我可以通过分配其存储来结束其生命周期.我想知道琐碎的析构函数是否可以延长对象的生命周期并通过“摧毁一个对象”来引起混淆问题,而这种对象已经结束了更早的生命周期. 首先,我所知道的是安全且无别名的 void* m
C11§3.8.1声明,对于具有普通析构函数的对象,我可以通过分配其存储来结束其生命周期.我想知道琐碎的析构函数是否可以延长对象的生命周期并通过“摧毁一个对象”来引起混淆问题,而这种对象已经结束了更早的生命周期.

首先,我所知道的是安全且无别名的

void* mem = malloc(sizeof(int));
int*  asInt = (int*)mem;
*asInt = 1; // the object '1' is now alive,trivial constructor + assignment
short*  asShort = (short*)mem;
*asShort = 2; // the object '1' ends its life,because I reassigned to its storage
              // the object '2' is now alive,trivial constructor + assignment
free(mem);    // the object '2' ends its life because its storage was released

现在,对于一些不太清楚的事情:

{
    int asInt = 3; // the object '3' is now alive,trivial constructor + assignment
    short* asShort = (short*)&asInt; // just creating a pointer
    *asShort = 4; // the object '3' ends its life,because I reassigned to its storage
                  // the object '4' is now alive,trivial constructor + assignment
    // implicitly,asInt->~int() gets called here,as a trivial destructor
}   // 'the object '4' ends its life,because its storage was released

§6.7.2规定自动存储持续时间的对象在范围的末尾被销毁,表明析构函数被调用.如果有一个要销毁的int,* asShort = 2是一个别名违规,因为我正在取消引用一个不相关类型的指针.但是如果整数的生命周期在* asShort = 2之前结束,那么我在一个short上调用一个int析构函数.

我看到几个相互竞争的部分:

§3.8.8读

If a program ends the lifetime of an object of type T with static (3.7.1),thread (3.7.2),or automatic (3.7.3)
storage duration and if T has a non-trivial destructor,39 the program must ensure that an object of the
original type occupies that same storage location when the implicit destructor call takes place; otherwise the
behavior of the program is undefined.

事实上,他们用非平凡的析构函数调用类型T作为产生未定义的行为似乎,对我来说,表明在该存储位置中使用一个简单的析构函数定义了不同的类型,但我在规范中的任何地方都找不到那定义了.

如果将一个简单的析构函数定义为noop,这样的定义会很容易,但是关于它们的规范却非常少.

§6.7.3表明允许goto进入和退出其变量具有普通构造函数和普通析构函数的作用域.这似乎暗示了允许跳过简单析构函数的模式,但是在范围末尾销毁对象的规范的前一部分没有提到这一点.

最后,有一个时髦的阅读:

§3.8.1表明如果我的构造函数很简单,我可以随时启动对象的生命周期.这似乎表明我可以做类似的事情

{
    int asInt = 3;
    short* asShort = (short*)&asInt;
    *asShort = 4; // the object '4' is now alive,trivial constructor + assignment
    // I declare that an object in the storage of &asInt of type int is
    // created with an undefined value.  Doing so reuses the space of
    // the object '4',ending its life.

    // implicitly,as a trivial destructor
}

这些读数中唯一似乎表明存在任何别名问题的是其自身的§6.7.2.看起来,当作为整个规范的一部分阅读时,平凡的析构函数不应以任何方式影响程序(尽管由于各种原因).有谁知道在这种情况下会发生什么?

解决方法

在您的第二个代码段中:
{
    int asInt = 3; // the object '3' is now alive,trivial constructor + assignment
    short* asShort = (short*)&asInt; // just creating a pointer
    *asShort = 4; 
    // Violation of strict aliasing. Undefined behavior. End of.
}

这同样适用于您的第一个代码段.它不是“安全的”,但它通常会起作用,因为(a)没有特别的理由要求编译器实现它不起作用,并且(b)在实践中编译器必须支持至少一些违反严格别名,否则就不可能使用编译器实现内存分配器.

我所知道的事情可以而且确实会激发编译器打破这种代码,如果你之后读取asInt,则允许DFA“检测”asInt未被修改(因为它仅通过严格别名违规进行修改,这是UB),并在写入* asShort后移动asInt的初始化.这是我们对标准的任何一种解释的UB – 在我的解释中,因为严格的混叠违规和你的解释,因为asInt在其生命周期结束后被读取.所以我们都为不工作而感到高兴.

但是我不同意你的解释.如果您认为分配asInt的部分存储结束asInt的生命周期,那么这就是自动对象的生命周期是其范围的语句的直接矛盾.好的,我们可以接受这是一般规则的例外.但这意味着以下内容无效:

{
    int asInt = 0;
    unsigned char *asChar = (unsigned char*)&asInt;
    *asChar = 0; // I've assigned the storage,so I've ended the lifetime,right?
    std::cout << asInt; // using an object after end of lifetime,undefined behavior!
}

除了允许unsigned char作为别名类型(以及定义all-bits-0对于整数类型意味着“0”)的全部要点是使代码像这样工作.因此,我非常不愿意对标准的任何部分进行解释,这意味着这不起作用.

Ben在下面的评论中给出了另一种解释,* asShort赋值只是不会结束asInt的生命周期.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读