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
Now, for something which is not so clear:
{
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 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 reads
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.
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.
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 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 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
}
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?