How to avoid strict aliasing errors when using ali

2019-06-20 14:25发布

问题:

I'm using std::aligned_storage as the backing storage for a variant template. The problem is, once I enable -O2 on gcc I start getting warnings of 'dereferencing type-punned pointer will break strict aliasing`.

The real template is much more complex (type checked at runtime), but a minimal example to generate the warning is:

struct foo
{
  std::aligned_storage<1024> data;

  // ... set() uses placement new, stores type information etc ...

  template <class T>
  T& get()
  {
    return reinterpret_cast<T&>(data); // warning: breaks strict aliasing rules
  }
};

I'm pretty sure boost::variant is doing essentially the same thing as this, but I can't seem to find how they avoid this issue.

My questions are:

  • If using aligned_storage in this way violates strict-aliasing, how should I be using it?
  • Is there actually a strict-aliasing problem in get() given that there are no other pointer based operations in the function?
    • What about if get() is inlined?
    • What about get() = 4; get() = 3.2? Could that sequence be reordered due to int and float being different types?

回答1:

std::aligned_storage is part of <type_traits>; like most of the rest of the inhabitants of that header file, it is just a holder for some typedefs and is not meant to be used as a datatype. Its job is to take a size and alignment, and make you a POD type with those characteristics.

You cannot use std::aligned_storage<Len, Align> directly. You must use std::aligned_storage<Len, Align>::type, the transformed type, which is "a POD type suitable for for use as uninitialized storage for any object whose size is at most Len and whose alignment is a divisor of Align." (Align defaults to the largest useful alignment greater than or equal to Len.)

As the C++ standard notes, normally the type returned by std::aligned_storage will be an array (of the specified size) of unsigned char with an alignment specifier. That avoids the "no strict aliasing" rule because a character type may alias any other type.

So you might do something like:

template<typename T>
using raw_memory = typename std::aligned_storage<sizeof(T),
                                                 std::alignment_of<T>::value>::type;

template<typename T>
void* allocate() { return static_cast<void*>(new raw_memory<T>); }

template<typename T, typename ...Arg>
T* maker(Arg&&...arg) {
   return new(allocate<T>()) T(std::forward<Arg>(arg)...);
}