Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid strict aliasing errors when using aligned_storage

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?
like image 211
marack Avatar asked Oct 07 '13 04:10

marack


People also ask

What is the strict aliasing rule?

The strict aliasing rule dictates that pointers are assumed not to alias if they point to fundamentally different types, except for char* and void* which can alias to any other data type.

Does c++ have strict aliasing?

In both C and C++ the standard specifies which expression types are allowed to alias which types. The compiler and optimizer are allowed to assume we follow the aliasing rules strictly, hence the term strict aliasing rule.


1 Answers

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)...);
}
like image 71
rici Avatar answered Oct 10 '22 09:10

rici