小的析构函数会导致混叠 [英] Do trivial destructors cause aliasing

查看:129
本文介绍了小的析构函数会导致混叠的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

C ++ 11§3.8.1声明,对于具有琐碎析构函数的对象,我可以通过分配给它的存储来结束其生命周期。我想知道,如果琐碎的析构函数可以延长对象的寿命,并通过破坏对象,我结束了更早的生命周期造成混乱的困扰。

C++11 §3.8.1 declares that, for an object with a trivial destructor, I can end its lifespan by assigning to its storage. I am wondering if trivial destructors can prolong the object's lifespan and cause aliasing woes by "destroying an object" that I ended the lifespan of much earlier.

我知道是安全和无别名的

To start, something which I know is safe and alias-free

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 之前结束,那么我将在短期内调用int析构函数。

§6.7.2 states that objects of automatic storage duration are destroyed at the end of the scope, indicating that the destructor gets called. If there is an int to destroy, *asShort = 2 is an aliasing violation because I am dereferencing a pointer of unrelated type. But if the integer's lifespan ended before *asShort = 2, then I am calling an int destructor on a short.

我看到几个相关的部分:

I see several competing sections regarding this:

§3.8.8读取


如果程序以静态(3.7.1),线程(3.7.2)或自动(3.7.3)
存储持续时间结束类型T的对象的生命周期,并且如果T具有非平凡析构函数39,当隐式析构函数调用发生时,程序必须确保
原始类型的对象占据相同的存储位置;否则程序的
行为是未定义的。

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对于我来说,产生未定义的行为似乎表明在具有简单析构函数的存储位置中具有不同类型是定义的,但是我在规范中找不到定义。

The fact that they call out types T with non-trivial destructor as yielding undefined behavior seems, to me, to indicate that having a different type in that storage location with a trivial destructor is defined, but I couldn't find anywhere in the spec that defined that.

如果一个简单的析构函数被定义为一个noop,但在规范中有很少的规定,这样的定义会很容易。

Such a definition would be easy if a trivial destructor was defined to be a noop, but there's remarkably little in the spec about them.

§6.7.3表明goto允许跳入和跳出范围,变量具有琐碎的构造函数和简单的析构函数。这似乎暗示了一种模式,其中允许跳过简单的析构函数,但是在范围结束时破坏对象的规范的早期部分没有提到这一点。

§6.7.3 indicates that goto's are allowed to jump into and out of scopes whose variables have trivial constructors and trivial destructors. This seems to suggest a pattern where trivial destructors are allowed to be skipped, but the earlier section from the spec on destroying objects at the end of the scope mentions none of this.

最后,有一个很好的阅读:

Finally, there's the sassy reading:

§3.8.1表示,如果它的构造函数是微不足道的,我可以在任何时候启动一个对象的生命周期。这似乎表明我可以做像

§3.8.1 indicates that I am allowed to start an object's lifespan any time I want, if its constructor is trivial. This seems to indicate that I could do something like

{
    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, asInt->~int() gets called here, as a trivial destructor
}

这些看起来似乎暗示任何别名问题的唯一一个是它自己的§6.7.2。看起来像,当读取作为整个规范的一部分,平凡的析构函数不应该影响程序以任何方式(虽然由于各种原因)。

The only one of these reading that seems to suggest any aliasing issues is §6.7.2 on its own. It seems like, when read as part of a whole spec, the trivial destructor should not affect the program in any way (though for various reasons). Does anyone know what happens in this situation?

推荐答案

在您的第二个代码段中:

In your second code snippet:

{
    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)在实践中编译器必须支持至少几个违反严格的别名,否则就不可能使用编译器实现内存分配器。

The same applies to your first code snippet. It is not "safe", but it will generally work because (a) there's no particular reason for a compiler to be implemented such that it doesn't work, and (b) in practice compilers have to support at least a few violations of strict aliasing or else it would be impossible to implement the memory allocator using the compiler.

我知道的事情可以而且确实让编译器打破这种代码是if您读取 asInt 之后,DFA允许检测 asInt 未修改(因为它只修改严格别名冲突,即UB),并在写入 * asShort 后移动 asInt 的初始化。这是UB的任何一个解释的标准虽然 - 在我的解释,因为严格的别名冲突和你的解释,因为 asInt 是在其生命周期结束后读取。所以我们都很高兴不能工作。

The thing that I know can and does provoke compilers to break this kind of code is if you read asInt afterwards, the DFA is allowed to "detect" that asInt is not modified (since it's modified only by the strict-alias violation, which is UB), and move the initialization of asInt after the write to *asShort. That's UB by either of our interpretations of the standard though -- in my interpretation because of the strict aliasing violation and in your interpretation because asInt is read after the end of its lifetime. So we're both happy for that not to work.

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

However I don't agree with your interpretation. If you consider that assigning to part of the storage of asInt ends the lifetime of asInt, then that's a direct contradiction of the statement that the lifetime of an automatic object is its scope. OK, so we might accept that this is an exception to the general rule. But that would mean that the following is not valid:

{
    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!
}

除了允许 / code>作为别名类型(并且定义所有位0对于整数类型意味着0)是使这样的代码工作。所以我很不愿意解释标准的任何部分,这意味着这不工作。

Except that the whole point of allowing unsigned char as an aliasing type (and of defining that all-bits-0 means "0" for integer types) is to make code like this work. So I'm very reluctant to make an interpretation of any part of the standard, which implies that this doesn't work.

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

Ben gives another interpretation in comments below, that the *asShort assignment simply doesn't end the lifetime of asInt.

这篇关于小的析构函数会导致混叠的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆