Это взломать, чтобы удалить предупреждение об альпинировании UB?

Мы просто обновили наш компилятор до gcc 4.6, и теперь мы получаем некоторые из этих предупреждений. На данный момент наша кодовая база не находится в состоянии, которое должно быть скомпилировано с помощью С++ 0x, и в любом случае мы не хотим запускать это в prod (по крайней мере, пока) - поэтому мне нужно было исправить это предупреждение.

Предупреждения происходят из-за чего-то вроде этого:

struct SomeDataPage
{
  // members
  char vData[SOME_SIZE];
};

позже, это используется следующим образом

SomeDataPage page;
new(page.vData) SomeType(); // non-trivial constructor

Чтобы читать, обновлять и возвращать, например, следующее приведение

reinterpret_cast<SomeType*>(page.vData)->some_member();

Это было нормально с 4.4; в 4.6 приведенное выше значение генерирует:

предупреждение: тип punned pointer будет нарушать правила строгого сглаживания

Теперь чистый способ удалить эту ошибку - использовать union, но, как я уже сказал, мы не можем использовать С++ 0x (и, следовательно, неограниченные объединения), поэтому я использовал ужасный hack ниже - теперь предупреждение ушло, но могу ли я вызвать демонов носа?

static_cast<SomeType*>(reinterpret_cast<void*>(page.vData))->some_member();

Это, похоже, работает нормально (см. простой пример здесь: http://www.ideone.com/9p3MS) и не генерирует никаких предупреждений, это нормально (не в стилистическом смысл) использовать это до С++ 0x?

ПРИМЕЧАНИЕ. Я не хочу использовать -fno-strict-aliasing в целом...

РЕДАКТИРОВАТЬ. Кажется, я ошибался, это же предупреждение существует в 4.4, я думаю, мы только недавно это сделали с изменением (это всегда было маловероятным для компилятора), вопрос все еще стоит.

EDIT: дальнейшее исследование дало некоторую интересную информацию, кажется, что выполнение роли и вызов функции-члена в одной строке является тем, что вызывает предупреждение, если код разбит на две строки следующим образом

SomeType* ptr = reinterpret_cast<SomeType*>(page.vData);
ptr->some_method();

это действительно не вызывает предупреждения. В результате мой простой пример на идеоне ошибочен и, что более важно, мой взломанный выше не исправляет предупреждение, единственный способ исправить это - разделить вызов функции от актера - тогда актерский состав может оставить как reinterpret_cast.

Ответ 1

SomeDataPage page;
new(page.vData) SomeType(); // non-trivial constructor
reinterpret_cast<SomeType*>(page.vData)->some_member();

Это было нормально с 4.4; в 4.6 приведенное выше значение генерирует:

warning: type punned pointer will break strict-aliasing rules

Вы можете попробовать:

SomeDataPage page;
SomeType *data = new(page.vData) SomeType(); // non-trivial constructor
data->some_member();

Ответ 2

Почему бы не использовать:

SomeType *item = new (page.vData) SomeType();

а затем:

item->some_member ();

Я не думаю, что союз - лучший способ, это может быть проблематично. Из документов gcc:

`-fstrict-aliasing'
 Allows the compiler to assume the strictest aliasing rules
 applicable to the language being compiled.  For C (and C++), this
 activates optimizations based on the type of expressions.  In
 particular, an object of one type is assumed never to reside at
 the same address as an object of a different type, unless the
 types are almost the same.  For example, an `unsigned int' can
 alias an `int', but not a `void*' or a `double'.  A character type
 may alias any other type.

 Pay special attention to code like this:
      union a_union {
        int i;
        double d;
      };

      int f() {
        a_union t;
        t.d = 3.0;
        return t.i;
      }
 The practice of reading from a different union member than the one
 most recently written to (called "type-punning") is common.  Even
 with `-fstrict-aliasing', type-punning is allowed, provided the
 memory is accessed through the union type.  So, the code above
 will work as expected.  However, this code might not:
      int f() {
        a_union t;
        int* ip;
        t.d = 3.0;
        ip = &t.i;
        return *ip;
      }

Как это связано с вашей проблемой, сложно определить. Я думаю, что компилятор не видит данные в SomeType так же, как данные в vData.

Ответ 3

Я бы больше беспокоился о том, что SOME_SIZE не был достаточно большим, честно говоря. Тем не менее, законно использовать псевдоним любого типа с помощью char*. Поэтому просто делать reinterpret_cast<T*>(&page.vData[0]) должно быть просто отлично.

Кроме того, я бы поставил под сомнение этот вид дизайна. Если вы не внедряете boost::variant или что-то подобное, нет оснований для его использования.

Ответ 4

struct SomeDataPage
{
  // members
  char vData[SOME_SIZE];
};

Это проблематично для причин сглаживания/выравнивания. Во-первых, выравнивание этой структуры не обязательно совпадает с тем типом, который вы пытаетесь качать внутри него. Вы можете попробовать использовать атрибуты GCC для обеспечения определенного выравнивания:

struct SomeDataPage { char vData[SOME_SIZE] __attribute__((aligned(16))); };

Если выравнивание 16 должно быть адекватным для всего, с чем я столкнулся. Опять же, компилятору все равно не понравится ваш код, но он не сломается, если выравнивание будет хорошим. Кроме того, вы можете использовать новый С++ 0x alignof/alignas.

template <class T>
struct DataPage {
   alignof(T) char vData[sizeof(T)];
};