c – 琐碎的析构函数会导致混叠
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读
事实上,他们用非平凡的析构函数调用类型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的生命周期. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |