строгое алиасинг и выравнивание

Мне нужен безопасный способ псевдонима между произвольными типами POD, соответствующий ISO-C ++ 11, явно учитывая 3.10 / 10 и 3.11 n3242 или новее. Здесь есть много вопросов о строгом псевдониме, большинство из которых касается C, а не C ++. Я нашел "решение" для C, которое использует союзы, вероятно, используя этот раздел

тип объединения, который включает в себя один из вышеупомянутых типов среди своих элементов или нестатических элементов данных

Из этого я построил это.

#include <iostream>

template <typename T, typename U>
T& access_as(U* p)
{
    union dummy_union
    {
        U dummy;
        T destination;
    };

    dummy_union* u = (dummy_union*)p;

    return u->destination;
}

struct test
{
    short s;
    int i;
};

int main()
{
    int buf[2];

    static_assert(sizeof(buf) >= sizeof(double), "");
    static_assert(sizeof(buf) >= sizeof(test), "");

    access_as<double>(buf) = 42.1337;
    std::cout << access_as<double>(buf) << '\n';

    access_as<test>(buf).s = 42;
    access_as<test>(buf).i = 1234;

    std::cout << access_as<test>(buf).s << '\n';
    std::cout << access_as<test>(buf).i << '\n';
}

У меня вопрос, на всякий случай, законна ли эта программа в соответствии со стандартом? *

Он не выдает никаких предупреждений и отлично работает при компиляции с MinGW / GCC 4.6.2, используя:

g++ -std=c++0x -Wall -Wextra -O3 -fstrict-aliasing -o alias.exe alias.cpp

* Изменить: А если нет, как можно изменить это, чтобы оно было законным?


person cooky451    schedule 01.04.2012    source источник
comment
Я на 99% уверен, что это незаконно в C ++ (вы читаете неактивного члена профсоюза). Однако я не уверен насчет C11, правила которого более мягкие.   -  person Kerrek SB    schedule 01.04.2012
comment
@KerrekSB он не читает неактивного члена профсоюза. Доступ к члену union остается доступом на запись к lvalue на протяжении всего доступа.   -  person Johannes Schaub - litb    schedule 01.04.2012
comment
Просто избавьтесь от этого dummy_union, и не должно возникнуть проблем при условии, что выравнивание типов совместимо.   -  person Johannes Schaub - litb    schedule 01.04.2012
comment
@ JohannesSchaub-litb: Что делает u->destination активным участником?   -  person Kerrek SB    schedule 01.04.2012
comment
@ JohannesSchaub-litb Я не совсем понимаю ... если я избавлюсь от фиктивного союза, я просто буду использовать одно и то же lvalue с указателями на разные типы, что, как я понимаю 3.10 / 10, не разрешено?   -  person cooky451    schedule 01.04.2012
comment
@KerrekSB не является активным участником. Но он тоже ничего не читает. И вообще не существует даже объекта объединения. Но поскольку это вообще не указано в C ++, мы не можем об этом говорить. Это черная дыра. Если убрать штуцер, будет намного меньше шума.   -  person Johannes Schaub - litb    schedule 01.04.2012
comment
@ cooky451 3.10 / 10 ничего подобного не говорит.   -  person Johannes Schaub - litb    schedule 01.04.2012
comment
@ JohannesSchaub-litb 3.10 / 10 говорит, через какие типы можно легально получить доступ к сохраненному значению объекта. Какой момент здесь применяется при снятии союза?   -  person cooky451    schedule 01.04.2012
comment
Укажите динамический тип объекта. Чтение объекта double через lvalue типа double нормально.   -  person Johannes Schaub - litb    schedule 01.04.2012
comment
@ JohannesSchaub-litb: Вы правы, код в main только пишет. Но это не что иное, как сказать *reinterpret_cast<double*>(buf) = 43.1337;, не так ли? Это то же нарушение псевдонима. Дело в том, что buf не указатель на подходящий объект объединения.   -  person Kerrek SB    schedule 01.04.2012
comment
И 3.10 / 10 не должны охватывать доступ для записи, чтобы что-либо имело смысл в пункте 3.8 / 3.10. Например, это полностью законно , если выравнивание совместимо: int a; float *b = (float*)&a; *b = 1.0f; std::cout << *b;. Вы читаете объект float по lvalue типа float.   -  person Johannes Schaub - litb    schedule 01.04.2012
comment
@KerrekSB, это не нарушение псевдонима. Он ничем не отличается от *(double*)malloc(sizeof(double)) = 43.1337;, просто в этом случае у нас есть гарантированные условия выравнивания. В этом случае значение l до записи также не относилось к объекту double.   -  person Johannes Schaub - litb    schedule 01.04.2012
comment
@ JohannesSchaub-litb: Но malloc гарантированно вернет указатель, подходящий для любого типа. Напротив, OP использует int*, полученный как адрес существующего объекта int.   -  person Kerrek SB    schedule 01.04.2012
comment
@KerrekSB, я не знаю, что вы имеете в виду, говоря, что это подходит для любого типа. Любое записываемое хранилище, при условии, что размер и выравнивание в порядке, подходит для любого объекта. В Стандарте нет ничего, что ограничивало бы их.   -  person Johannes Schaub - litb    schedule 01.04.2012
comment
@ JohannesSchaub-litb Ну ладно, когда я действительно ввожу новый псевдоним, это законно? По крайней мере, GCC не предупреждает. Очень интересно. Есть ли способ дополнительно справиться с выравниванием (или, по крайней мере, assert () это или что-то в этом роде)? Было бы здорово наконец получить полностью соответствующее решение.   -  person cooky451    schedule 01.04.2012
comment
@KerrekSB очень важно, чтобы это работало, потому что именно так работают распределители, которые перерабатывают хранилище старых объектов.   -  person Johannes Schaub - litb    schedule 01.04.2012
comment
@ JohannesSchaub-litb: Однако распределители обычно не получают свою память из базовой памяти автоматических объектов. Системные функции распределения возвращают более обширные указатели, чем &x для некоторых случайных x.   -  person Kerrek SB    schedule 01.04.2012
comment
@KerrekSB Текущий стандарт позволяет коду получать память из произвольных источников и использовать их для создания новых объектов. Но теперь, когда вы это говорите, IIRC, есть решение проблемы, которое отображает это значение undefined (принимая память автоматического / статического объекта не-символьного типа), хотя оно не является частью C ++ 11.   -  person Johannes Schaub - litb    schedule 01.04.2012
comment
@ JohannesSchaub-litb: Но, по крайней мере, получаемая вами память должна быть согласована с типом, который вы хотите использовать. Допустим, что int не имеет деструктора (с эффектами), поэтому вы можете использовать эту память, но вы можете в первую очередь использовать ее только для хранения в ней других int. Все остальное требует дополнительных гарантий.   -  person Kerrek SB    schedule 01.04.2012
comment
@KerrekSB open-std.org/jtc1/sc22/ wg21 / docs / cwg_active.html # 1116. См. Правило Если программа получает хранилище для определенного типа ...   -  person Johannes Schaub - litb    schedule 01.04.2012
comment
Обратите внимание, что gcc / g ++ дает некоторые дополнительные гарантии относительно псевдонимов в объединении, которые не являются частью стандарта C ++. То есть некое объединение псевдонимов, которое явно недопустимо в стандарте, гарантированно работает в gcc / g ++. Однако я не уверен, применимо ли это здесь.   -  person celtschk    schedule 01.04.2012
comment
@celtschk Что ж, это могло бы объяснить пропавшее предупреждение и быть намеком на то, что этот способ (пока) не соответствует требованиям.   -  person cooky451    schedule 01.04.2012
comment
Примите участие в расширенных обсуждениях в чате переполнения стека.   -  person Tim Post♦    schedule 01.04.2012
comment
@ cooky451: соответствующий компилятор может определять неопределенное поведение. Кроме того, для неопределенного поведения диагностика не требуется (а иногда даже невозможна). Неопределенное поведение просто означает, что реализация может делать все, что захочет.   -  person celtschk    schedule 01.04.2012
comment
@TimPost Примите участие в расширенных обсуждениях в Stack Overflow Chat. Но я думаю, что заключение обсуждения принадлежит к этому, не так ли?   -  person curiousguy    schedule 20.07.2012


Ответы (4)


Это никогда не будет законным, независимо от того, какие искажения вы совершаете со странными слепками, союзами и прочим.

Фундаментальный факт заключается в следующем: два объекта разных типов могут никогда не иметь псевдонимов в памяти, за некоторыми исключениями (см. Ниже).

Пример

Рассмотрим следующий код:

void sum(double& out, float* in, int count) {
    for(int i = 0; i < count; ++i) {
        out += *in++;
    }
}

Давайте разберем это на локальные регистровые переменные, чтобы более точно смоделировать фактическое выполнение:

void sum(double& out, float* in, int count) {
    for(int i = 0; i < count; ++i) {
        register double out_val = out; // (1)
        register double in_val = *in; // (2)
        register double tmp = out_val + in_val;
        out = tmp; // (3)
        in++;
    }
}

Предположим, что (1), (2) и (3) представляют собой чтение, чтение и запись в память соответственно, что может оказаться очень дорогостоящими операциями в таком жестком внутреннем цикле. Разумной оптимизацией этого цикла была бы следующая:

void sum(double& out, float* in, int count) {
    register double tmp = out; // (1)
    for(int i = 0; i < count; ++i) {
        register double in_val = *in; // (2)
        tmp = tmp + in_val;
        in++;
    }
    out = tmp; // (3)
}

Эта оптимизация снижает количество необходимых операций чтения памяти наполовину, а количество операций записи в память до 1. Это может иметь огромное влияние на производительность кода и является очень важной оптимизацией для всех оптимизирующих компиляторов C и C ++.

Теперь предположим, что у нас нет строгого псевдонима. Предположим, что запись в объект любого типа может повлиять на любой другой объект. Предположим, что запись в double может где-то повлиять на значение числа с плавающей запятой. Это делает вышеупомянутую оптимизацию подозрительной, потому что возможно, что программист на самом деле предназначил для out и in to псевдоним, чтобы результат функции суммы был более сложным и зависел от процесса. Звучит глупо? Даже в этом случае компилятор не может различить «глупый» и «умный» код. Компилятор может различать только правильно сформированный и плохо сформированный код. Если мы разрешаем свободное алиасинг, то компилятор должен быть консервативным в своих оптимизациях и должен выполнять дополнительное хранилище (3) на каждой итерации цикла.

Надеюсь, теперь вы понимаете, почему такой союз или уловка не могут быть законными. Подобные фундаментальные концепции нельзя обойти ловкостью рук.

Исключения из строгого алиасинга

Стандарты C и C ++ делают специальное положение для псевдонима любого типа с char и с любым «связанным типом», который, среди прочего, включает производные и базовые типы и члены, потому что возможность независимо использовать адрес члена класса очень важна. Вы можете найти исчерпывающий список этих положений в этом ответе.

Более того, GCC делает специальные условия для чтения из другого члена союза, чем тот, в который была записана последняя запись. Обратите внимание, что такой тип преобразования через объединение фактически не позволяет вам нарушать псевдонимы. Только один член союза может быть активным одновременно, поэтому, например, даже с GCC следующее поведение будет неопределенным:

union {
    double d;
    float f[2];
};
f[0] = 3.0f;
f[1] = 5.0f;
sum(d, f, 2); // UB: attempt to treat two members of
              // a union as simultaneously active

Обходные пути

Единственный стандартный способ интерпретировать биты одного объекта как биты объекта другого типа - использовать эквивалент memcpy. При этом используется специальное положение для псевдонимов с char объектами, что по сути позволяет вам читать и изменять базовое представление объекта на уровне байтов. Например, следующее является законным и не нарушает строгих правил псевдонима:

int a[2];
double d;
static_assert(sizeof(a) == sizeof(d));
memcpy(a, &d, sizeof(d));

Это семантически эквивалентно следующему коду:

int a[2];
double d;
static_assert(sizeof(a) == sizeof(d));
for(size_t i = 0; i < sizeof(a); ++i)
   ((char*)a)[i] = ((char*)&d)[i];

GCC обеспечивает чтение из неактивного члена объединения, неявно делая его активным. Из документации GCC:

Распространена практика чтения от другого члена профсоюза, чем тот, которому в последний раз писали (так называемая «каламбур»). Даже с -fstrict-aliasing разрешено использование символов при условии, что доступ к памяти осуществляется через тип объединения. Итак, приведенный выше код будет работать должным образом. См. Перечисления объединений структур и реализация битовых полей. Однако этот код не может:

int f() {
    union a_union t;
    int* ip;
    t.d = 3.0;
    ip = &t.i;
    return *ip;
}

Точно так же доступ путем взятия адреса, приведения результирующего указателя и разыменования результата имеет неопределенное поведение, даже если при приведении используется тип объединения, например:

int f() {
    double d = 3.0;
    return ((union a_union *) &d)->i;
} 

Размещение новое

(Примечание: здесь я использую память, поскольку у меня сейчас нет доступа к стандарту). После того, как вы разместите новый объект в буфере хранилища, время жизни нижележащих объектов хранилища неявно заканчивается. Это похоже на то, что происходит, когда вы пишете члену союза:

union {
    int i;
    float f;
} u;

// No member of u is active. Neither i nor f refer to an lvalue of any type.
u.i = 5;
// The member u.i is now active, and there exists an lvalue (object)
// of type int with the value 5. No float object exists.
u.f = 5.0f;
// The member u.i is no longer active,
// as its lifetime has ended with the assignment.
// The member u.f is now active, and there exists an lvalue (object)
// of type float with the value 5.0f. No int object exists.

Теперь давайте посмотрим на что-то похожее с place-new:

#define MAX_(x, y) ((x) > (y) ? (x) : (y))
// new returns suitably aligned memory
char* buffer = new char[MAX_(sizeof(int), sizeof(float))];
// Currently, only char objects exist in the buffer.
new (buffer) int(5);
// An object of type int has been constructed in the memory pointed to by buffer,
// implicitly ending the lifetime of the underlying storage objects.
new (buffer) float(5.0f);
// An object of type int has been constructed in the memory pointed to by buffer,
// implicitly ending the lifetime of the int object that previously occupied the same memory.

Этот вид неявного завершения срока службы может происходить только для типов с тривиальными конструкторами и деструкторами по очевидным причинам.

person anttirt    schedule 02.04.2012
comment
Если два объекта разного типа никогда не могут быть псевдонимами в памяти, за некоторыми исключениями, это фундаментальный факт, как работает размещение нового? (Его нет в списке исключений.) - person cooky451; 02.04.2012
comment
@ cooky451 Я добавил раздел о размещении новых. - person anttirt; 02.04.2012
comment
Это лучший ресурс, который я когда-либо видел по теме исключений псевдонима и псевдонима. Отличный ответ. - person gmbeard; 23.05.2018

Помимо ошибки, когда sizeof(T) > sizeof(U), проблема может заключаться в том, что объединение имеет соответствующее и, возможно, более высокое выравнивание, чем U, из-за T. Если вы не создадите экземпляр этого объединения, чтобы его блок памяти был выровнен (и был достаточно большим!), А затем извлеките член с целевым типом T, в худшем случае он будет тихо сломан.

Например, ошибка выравнивания возникает, если вы выполняете приведение U* в стиле C, где U требует выравнивания 4 байта, к dummy_union*, где dummy_union требует выравнивания до 8 байтов, потому что alignof(T) == 8. После этого вы, возможно, прочитаете член объединения с типом T, выровненным по 4 вместо 8 байтов.


Приведение псевдонима (выравнивание и безопасный размер reinterpret_cast только для POD):

Это предложение явно нарушает строгий псевдоним, но со статическими утверждениями:

///@brief Compile time checked reinterpret_cast where destAlign <= srcAlign && destSize <= srcSize
template<typename _TargetPtrType, typename _ArgType>
inline _TargetPtrType alias_cast(_ArgType* const ptr)
{
    //assert argument alignment at runtime in debug builds
    assert(uintptr_t(ptr) % alignof(_ArgType) == 0);

    typedef typename std::tr1::remove_pointer<_TargetPtrType>::type target_type;
    static_assert(std::tr1::is_pointer<_TargetPtrType>::value && std::tr1::is_pod<target_type>::value, "Target type must be a pointer to POD");
    static_assert(std::tr1::is_pod<_ArgType>::value, "Argument must point to POD");
    static_assert(std::tr1::is_const<_ArgType>::value ? std::tr1::is_const<target_type>::value : true, "const argument must be cast to const target type");
    static_assert(alignof(_ArgType) % alignof(target_type) == 0, "Target alignment must be <= source alignment");
    static_assert(sizeof(_ArgType) >= sizeof(target_type), "Target size must be <= source size");

    //reinterpret cast doesn't remove a const qualifier either
    return reinterpret_cast<_TargetPtrType>(ptr);
}

Использование с аргументом типа указателя (например, стандартные операторы приведения, такие как reinterpret_cast):

int* x = alias_cast<int*>(any_ptr);

Другой подход (позволяет избежать проблем с выравниванием и псевдонимом с помощью временного объединения):

template<typename ReturnType, typename ArgType>
inline ReturnType alias_value(const ArgType& x)
{
    //test argument alignment at runtime in debug builds
    assert(uintptr_t(&x) % alignof(ArgType) == 0);

    static_assert(!std::tr1::is_pointer<ReturnType>::value ? !std::tr1::is_const<ReturnType>::value : true, "Target type can't be a const value type");
    static_assert(std::tr1::is_pod<ReturnType>::value, "Target type must be POD");
    static_assert(std::tr1::is_pod<ArgType>::value, "Argument must be of POD type");

    //assure, that we don't read garbage
    static_assert(sizeof(ReturnType) <= sizeof(ArgType),"Target size must be <= argument size");

    union dummy_union
    {
        ArgType x;
        ReturnType r;
    };

    dummy_union dummy;
    dummy.x = x;

    return dummy.r;
}

Использование:

struct characters
{
    char c[5];
};

//.....

characters chars;

chars.c[0] = 'a';
chars.c[1] = 'b';
chars.c[2] = 'c';
chars.c[3] = 'd';
chars.c[4] = '\0';

int r = alias_value<int>(chars);

Недостатком этого является то, что объединение может потребовать больше памяти, чем фактически необходимо для ReturnType.


Обернутый memcpy (позволяет обойти проблемы с выравниванием и псевдонимом с помощью memcpy):

template<typename ReturnType, typename ArgType>
inline ReturnType alias_value(const ArgType& x)
{
    //assert argument alignment at runtime in debug builds
    assert(uintptr_t(&x) % alignof(ArgType) == 0);

    static_assert(!std::tr1::is_pointer<ReturnType>::value ? !std::tr1::is_const<ReturnType>::value : true, "Target type can't be a const value type");
    static_assert(std::tr1::is_pod<ReturnType>::value, "Target type must be POD");
    static_assert(std::tr1::is_pod<ArgType>::value, "Argument must be of POD type");

    //assure, that we don't read garbage
    static_assert(sizeof(ReturnType) <= sizeof(ArgType),"Target size must be <= argument size");

    ReturnType r;
    memcpy(&r,&x,sizeof(ReturnType));

    return r;
}

Для массивов динамического размера любого типа POD:

template<typename ReturnType, typename ElementType>
ReturnType alias_value(const ElementType* const array,const size_t size)
{
    //assert argument alignment at runtime in debug builds
    assert(uintptr_t(array) % alignof(ElementType) == 0);

    static const size_t min_element_count = (sizeof(ReturnType) / sizeof(ElementType)) + (sizeof(ReturnType) % sizeof(ElementType) != 0 ? 1 : 0);

    static_assert(!std::tr1::is_pointer<ReturnType>::value ? !std::tr1::is_const<ReturnType>::value : true, "Target type can't be a const value type");
    static_assert(std::tr1::is_pod<ReturnType>::value, "Target type must be POD");
    static_assert(std::tr1::is_pod<ElementType>::value, "Array elements must be of POD type");

    //check for minimum element count in array
    if(size < min_element_count)
        throw std::invalid_argument("insufficient array size");

    ReturnType r;
    memcpy(&r,array,sizeof(ReturnType));
    return r;
}

Более эффективные подходы могут выполнять явное невыровненное чтение с помощью встроенных функций, таких как SSE, для извлечения примитивов.


Примеры:

struct sample_struct
{
    char c[4];
    int _aligner;
};

int test(void)
{
    const sample_struct constPOD    = {};
    sample_struct pod               = {};
    const char* str                 = "abcd";

    const int* constIntPtr  = alias_cast<const int*>(&constPOD);
    void* voidPtr           = alias_value<void*>(pod);
    int intValue            = alias_value<int>(str,strlen(str));

    return 0;
}

РЕДАКТИРОВАТЬ:

  • Утверждения, обеспечивающие преобразование только POD, могут быть улучшены.
  • Убраны лишние хелперы шаблонов, теперь используются только трейты tr1
  • Статические утверждения для разъяснения и запрета на возвращаемый тип константного значения (не указателя)
  • Утверждения времени выполнения для отладочных сборок
  • Добавлены квалификаторы const к некоторым аргументам функции
  • Другой тип каламбура с использованием memcpy
  • Рефакторинг
  • Небольшой пример
person Community    schedule 01.04.2012
comment
Со вторым static_assert это даже не позволило бы преобразовать char * в int * на большинстве машин. - person cooky451; 01.04.2012
comment
@ cooky451: Предлагаемая функция предназначена исключительно для приведения союзов. - person jweyrich; 01.04.2012
comment
На самом деле, я частично неправильно понял cooky451, char, скорее всего, имеет меньшее выравнивание, чем int, но в любом случае безопасное приведение выравнивания теперь должно работать. - person Sam; 01.04.2012
comment
У него все та же проблема. (Или я что-то здесь наблюдаю.) - Считайте, что SourceType - это char, а dest_type - как int, и второй static_assert завершится ошибкой. - person cooky451; 01.04.2012
comment
Это сделано для того, чтобы избежать невыровненного чтения, поскольку int может быть выровнен по четырем байтам, а char - по одному. Если вы хотите преобразовать 4 символа в int, вы должны создать экземпляр своего объединения, чтобы символы имели то же выравнивание, что и int, и все. Просто никогда не возвращайте указатель или ссылку на член объединения в стеке. - person Sam; 01.04.2012
comment
Что ж, приведение от char * к int * должно работать. Если он не компилируется на странных машинах, это нормально, но, по крайней мере, его поведение четко определено. (Как я надеюсь.) - Но позвольте мне спросить: это учитывает выравнивание, но вы имеете в виду строгое наложение? - person cooky451; 01.04.2012
comment
Ваше последнее редактирование фактически копирует значения, а я не хочу этого. memcpy уже неплохо справляется с этим. ;) - person cooky451; 01.04.2012
comment
Ваш ответ мог бы быть memcpy, но это не весело :-) И строгий псевдоним здесь не является проблемой. Вам обязательно нужно каким-то образом скопировать исходную память, если выравнивание возможно неправильное. Проверка согласованности во время выполнения ни к чему не приведет. - person Sam; 01.04.2012
comment
Некоторые обновления; Любые предложения по улучшению будут оценены - person Sam; 04.04.2012

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

person Puppy    schedule 01.04.2012
comment
Как вы думаете, просто нет способа получить безопасный способ создания псевдонимов между произвольными типами POD? Мне это немного неловко ... наверное, самый либеральный язык, который я знаю, не позволяет мне делать то, что мне нужно делать? :( - Вы читали комментарии? Что вы скажете на предложения Йоханнеса? - person cooky451; 01.04.2012
comment
@ cooky451: Мне это немного неловко .. Это потому, что то, что вы пытаетесь сделать, неудобно для C ++. - person Nicol Bolas; 01.04.2012
comment
@NicolBolas На самом деле, я даже не думаю, что это так уж необычно. Я просто хочу получить доступ к простой памяти (что совершенно верно, что показано правилом char * extra и показано тем фактом, что большинство компиляторов даже не беспокоятся о строгом псевдониме), не ограничиваясь char * s. - person cooky451; 01.04.2012
comment
@ cooky451: Что неудобно, потому что правила системы типов C ++ не позволяют вам этого делать. - person Nicol Bolas; 01.04.2012
comment
@NicolBolas Круто, так что ... в принципе невозможно реализовать какие-либо производительные алгоритмы хеширования стандартным образом? Потому что там вам нужно заниматься целочисленной математикой; но язык не позволит вам этого сделать, потому что вы можете полагаться только на char * для работы? Должен быть какой-то способ. - person cooky451; 01.04.2012
comment
@ cooky451: Должен быть какой-то способ. Нет, нет; C ++ не требует делать все в точности так, как вы хотите. Кроме того, кто сказал, что с char* нельзя эффективно реализовать хеширование? Просто потяните последовательности из четырех char*; не похоже, что вы действительно заботитесь о порядке байтов объекта. Либо так, либо вы можете просто согласиться с тем, что ваш код живет в области, определяемой реализацией. Раньше это никого не останавливало ... - person Nicol Bolas; 01.04.2012
comment
@NicolBolas: Раньше это никого не останавливало: ты мне нравишься. Это прекрасно отражает то, как люди просто продолжают это делать :) - person xtofl; 01.04.2012
comment
@NicolBolas Что ж, вы наверняка хотите использовать сдвиг, поворот и все такое хорошее, но я полагаю, что эмуляция с 4-мя символами мучительно медленная. Что касается жизни на земле, определяемой реализацией: меня это устраивает. Но самое меньшее, чего я хочу достичь, - это ошибка времени компиляции в системах, у которых есть другие требования. - person cooky451; 01.04.2012
comment
@ cooky451: Конечно, нет безопасного способа использовать псевдонимы между произвольными типами. Это практически точный текст строгого правила псевдонима - вы не можете использовать псевдоним, кроме char*. - person Puppy; 01.04.2012
comment
@ cooky451: Повторная интерпретация типа как символов или целых чисел всегда дает результаты, зависящие от платформы. То, что вы пытаетесь сделать, можно сделать на C ++ эффективно, но не переносимо. (Если, например, каждый тип не реализует свою собственную переносимую функцию хеширования.) - person David Schwartz; 01.04.2012
comment
Несмотря на то, что псевдоним как таковой на самом деле невозможен, предполагаемый эффект - переинтерпретация значения из одного типа POD в другой - достижим безопасно и согласованно с использованием memcpy. См .: dbp-consulting.com/tutorials/StrictAliasing.html. - person Lubo Antonov; 02.04.2012
comment
@ lucas1024: Это не позволяет переинтерпретировать указатель .. только значение, которое не является предполагаемым эффектом. - person Puppy; 06.04.2012

У меня вопрос, на всякий случай, легальна ли эта программа в соответствии со стандартом?

Нет. Использование указанного вами псевдонима может быть неестественным. Написанный вами союз просто перемещает суть псевдонима. Может показаться, что это работает, но эта программа может дать сбой при изменении параметров ЦП, ABI или настроек компилятора.

А если нет, как можно сделать это законным?

Создавайте естественные временные переменные и относитесь к своему хранилищу как к BLOB-объекту памяти (вход и выход из BLOB-объекта в / из временных) или используйте объединение, которое представляет все ваши типы (помните, здесь по одному активному элементу за раз).

person justin    schedule 01.04.2012
comment
Чтобы прояснить это. Как предположил Йоханнес Шауб, является ли это псевдонимом законным и не противоречит ли каким-либо правилам? ideone.com/GBhqV - person cooky451; 01.04.2012
comment
@ cooky451 это незаконно. вот подход к записи в BLOB-объекты памяти (конечно, в какой-то момент вам также нужно будет читать из них): char buf[sizeof(LargestPODType)]; \n int temp(5); /* << the natural temporary */ \n /* now move bytes from temp to buf by casting &temp as a char* - or memcpy */ \n - person justin; 01.04.2012
comment
Я согласен и предпочитаю memcpy, поскольку он не оставляет места для двусмысленности. Вот источник, в котором подробно обсуждалась вся проблема: dbp-consulting.com/tutorials/StrictAliasing .html. - person Lubo Antonov; 02.04.2012