Ответ 1
Этот код нарушает строгие правила псевдонимов, что делает его незаконным для доступа к объекту с помощью указателя другого типа, хотя доступ через * char ** разрешен. Компилятору разрешено предположить, что указатели разных типов не указывают на одну и ту же память и соответственно оптимизируют. Это также означает, что код вызывает undefined поведение и может действительно что-то сделать.
Одна из лучших ссылок для этой темы - Понимание строгого сглаживания, и мы можем видеть, что первый пример аналогичен в OP код:
uint32_t swap_words( uint32_t arg )
{
uint16_t* const sp = (uint16_t*)&arg;
uint16_t hi = sp[0];
uint16_t lo = sp[1];
sp[1] = hi;
sp[0] = lo;
return (arg);
}
В статье объясняется, что этот код нарушает правила строгого сглаживания, поскольку sp
является псевдонимом arg
, но они имеют разные типы и говорят, что, хотя он будет скомпилирован, скорее всего, arg
не изменится после того, как swap_words
вернется, Хотя с помощью простых тестов я не могу воспроизвести этот результат либо с кодом выше, либо с кодом OP, но это ничего не значит, поскольку это поведение undefined и, следовательно, не предсказуемо.
В статье рассказывается о многих разных случаях и представлено несколько рабочих решений, в том числе тип-punning через объединение, которое хорошо определено в C99 1 и может быть undefined в С++, но на практике поддерживается большинством основных компиляторов, например, здесь ссылка gcc на запись типа. Предыдущая тема Цель союзов на C и С++ входит в подробности. Хотя в этой теме много потоков, это, кажется, делает лучшую работу.
Код для этого решения выглядит следующим образом:
typedef union
{
uint32_t u32;
uint16_t u16[2];
} U32;
uint32_t swap_words( uint32_t arg )
{
U32 in;
uint16_t lo;
uint16_t hi;
in.u32 = arg;
hi = in.u16[0];
lo = in.u16[1];
in.u16[0] = lo;
in.u16[1] = hi;
return (in.u32);
}
Для справки соответствующий раздел из черновик проекта C99 в строгом псевдониме 6.5
выражает пункт 7, в котором говорится:
Объект должен иметь сохраненное значение, доступ к которому может получить только выражение lvalue, которое имеет один из следующих типов: 76)
- тип, совместимый с эффективным типом объекта,
- квалифицированная версия типа, совместимая с эффективным типом объекта,
- тип, который является подписанным или неподписанным типом, соответствующим эффективному типу Объект,
- тип, который является подписанным или неподписанным типом, соответствующим квалифицированной версии эффективный тип объекта,
- совокупность или тип объединения, который включает один из вышеупомянутых типов среди его члены (в том числе, рекурсивно, член субагрегата или объединенного союза) или
- тип символа.
и в сноске 76 говорится:
Цель этого списка - указать те обстоятельства, при которых объект может или не может быть сглажен.
а соответствующий раздел из код проекта С++ - 3.10
Lvalues и rvalues para 10
Статья Type-punning and strict-aliasing дает более мягкое, но менее полное введение в тему и C99 revisited дает глубокий анализ C99 и сглаживания и не является легким чтением. Этот ответ Доступ к неактивному члену профсоюза - undefined? перебирает грязные данные о типе-пуринге через объединение в С++ и также не является легким.
Сноска:
- Цитата comment от Pascal Cuoq: [...] C99, который изначально был неуклюже сформулирован, появлялся, чтобы совершать пушки через союзы undefined. На самом деле, тип-караунд, хотя профсоюзы являются законными на C89, законными на C11, и он был законным на C99 все время, хотя до 2004 года потребовалось, чтобы комитет исправил неправильную формулировку и последующий выпуск TC3. open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm