aligned_storage and strict aliasing
Asked Answered
D

1

29

I'm currently using aligned_storage to implement an 'Optional' type similar to that of boost::optional. To accomplish this I have a class member like so:

typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type t_;

I use placement new to create the object, however I don't store the pointer returned anywhere. Instead, I access the underlying type of the object in all my member functions like this (obviously with checks to ensure the object is valid via a boolean flag also stored in my Optional type):

T const* operator->() const {
    return static_cast<T const*>(static_cast<void const*>(&t_));
}

My question is whether this is safe. My understanding is that my usage of placement new changes the 'dynamic type' of the object, and as long as I keep accessing the memory using that type I'll be okay. However I'm not clear on whether I have to hold the pointer returned from the placement new or whether I'm allowed to just cast to the underlying type whenever I need to access it. I have read section 3.10 of the C++11 standard, however I'm not fluent enough in standardese to be sure.

If possible, I would feel better if you could give reference to the standard in your answer (it helps me sleep at night :P).

Dunne answered 20/11, 2012 at 4:20 Comment(3)
I think this comes down to whether the pointer returned by operator new may be different than the address of the resulting object. Section 5.3.4.14 of the C++03 standard indicates that it will not necessarily be the same if the object is an array, which leads me to believe that it will be the same otherwise.Sexism
Ultimately, it's implementation defined as to whether or not the base address of the allocation is the first byte of the space occupied by the object. Therefore, it is more correct to take the result of new. The obvious case is allocating via new[]; Implementations often store information needed to destruct the array's objects in the leading bytes of the allocation.Theodicy
Note there is std::optional in C++17...Yoon
L
14

ABICT your use is safe.

  • Placement new of an object of type T will create an object starting at the address passed in.

§5.3.4/10 says:

A new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array.

For a non-array object, the size allocated cannot be greater that the size of the object, so the object representation must start at the beginning of the allocated memory in order to fit.

Placement new returns the pointer passed in (see § 18.6.1.3/2) as the result of the "allocation", so the object representation of the constructed object will start at that address.

  • static_cast<> and implicit conversions between T* type and void* convert between a pointer to the object and a pointer to its storage, if the object is a complete object.

§4.10/2 says:

A prvalue of type “pointer to cv T,” where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of the storage location where the object of type T resides, as if the object is a most derived object (1.8) of type T [...]

This defines the implicit conversion to convert as stated. Further §5.2.9[expr.static.cast]/4 defines static_cast<> for explicit conversions, where an implicit conversion exists to have the same effect as the implicit conversion:

Otherwise, an expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration T t(e); is well-formed, for some invented temporary variable t (8.5). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. [...]

For the inverse static_cast<> (from void* to T*), §5.2.9/13 states:

A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T,” where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. [...] A value of type pointer to object converted to “pointer to cv void” and back, possibly with different cv-qualification, shall have its original value.

So if you have a void* pointing to the storage of the T object (which is the pointer value that would result from the implicit conversion of a T* to the object, then a static_cast of that to a T* will produce a valid pointer to the object.

Returning to your question, the preceding points imply that if you have

typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type t_;
void * pvt_ = &t_;

T* pT = new (&t_) T(args...);
void * pvT = pT;

then

  • the storage of *pT exactly overlays the first size(T) bytes of the storage of t_, so that pvT == pvt_
  • pvt_ == static_cast<void*>(&t_)
  • static_cast<T*>(pvT) == pT
  • Taken together that yields static_cast<T*>(static_cast<void*>(&t_)) == pT
Longmire answered 28/1, 2013 at 7:25 Comment(4)
What does "ABICT" stand for?Psychologize
If an object is only accessed via pointers received through placement new, I see no reason why a compiler should care about the object's declared type since no accesses using that type could alias accesses made with the new type. If an object is written "directly" as an lvalue and its address is later converted to another type through placement new, requiring the compiler to perform the write to the original value not be reordered past writes as the new type could in some cases impede optimizations. I think the benefits of guaranteeing behavior in that case would outweigh...Ottoman
...the slight potential impediment to optimization, but I'm not sure compiler writers would share that judgment. Much of the value of alignment directives, however, would stem from the ability to have storage of static or automatic duration which may be used to hold things of known maximum size but arbitrary types.Ottoman
"As best I can tell", ABICT?Luettaluevano

© 2022 - 2024 — McMap. All rights reserved.