Может ли соответствующий компилятор нарушить преобразования uint32_t -> int16_t -> int32_t? - PullRequest
9 голосов
/ 10 февраля 2012

Недавно мы обнаружили странное поведение в каком-то старом коде.Этот код работал целую вечность, но сломался на некоторой платформе (XBox 360, PowerPC) с оптимизацией компилятора, включенной макс.Обычно я подозреваю неопределенное поведение.

Код выглядит примерно так:

#include <stdint.h>
uint32_t sign_extend16(uint32_t val)
{
   return (int32_t)(int16_t)val;
}

Это часть эмулятора, поэтому данная операция не должна быть слишком странной.Обычно я ожидал бы, что это будет учитывать только младшие 16 бит и расширять знак до 32 бит.По-видимому, это было поведение, которое было на протяжении веков.На x86_64 GCC дает мне такой результат:

0000000000000000 <sign_extend16>:
   0:   0f bf c7                movswl %di,%eax
   3:   c3                      retq

Однако из того, что я мог понять о стандарте, преобразование неподписанного в подписанное не определено, если не представляется возможным представить значение без знакаодин со знаком типа.

Может ли компилятор предположить, что значение без знака должно быть в диапазоне [0, 32767], так как любое другое значение будет неопределенным?В этом случае приведение к int16_t и еще одно приведение к int32_t ничего не сделают.В этом случае будет ли законным, если компилятор переведет код в простой ход?

Ответы [ 4 ]

9 голосов
/ 10 февраля 2012

Преобразование между двумя целочисленными типами никогда не является неопределенным поведением.

Но некоторые целочисленные преобразования определяются реализацией.

В целочисленных преобразованиях C говорит:

(C99, 6.3.1.3p3) "В противном случае новый тип подписывается, и значение не может быть представлено в нем; либо результат определяется реализацией, либо генерируется определяемый реализацией сигнал."

что gcc в этом случае задокументировано здесь:

http://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html

"Для преобразования в тип ширины N значение уменьшается по модулю 2 ^ N дов пределах диапазона типа; сигнал не подается "

2 голосов
/ 10 февраля 2012

Как говорит ouah , преобразование значения вне диапазона дает результат, определенный реализацией (или позволяет повысить сигнал, определяемый реализацией).

Например,для реализации было бы совершенно правильно сказать, что преобразование значения вне диапазона в int16_t сохраняет только младшие 15 битов значения и всегда устанавливает бит знака в 0. Таким образом, это будет интерпретировать ваш sign_extend16() функция просто return val & 0x7fff;.

Однако реализация не может интерпретировать вашу функцию так, что она просто возвращает val без изменений - преобразование, определенное реализацией, в int16_tдолжно приводить к значению где-то в диапазоне int16_t, поэтому конечный результат должен находиться где-то в [0, 32767] или [4294934528, 4294967295].

Обратите также внимание, что приведение int32_t там совершенно излишне.

Две альтернативы, которые не зависят от преобразований, определенных реализацией: (обратите внимание на изменение типа аргумента val):

uint32_t se16(uint16_t val)
{
    return -((uint32_t)val << 1 & 0x10000) | val;
}


uint32_t se16(uint16_t val)
{
    return (val ^ (uint32_t)32768) - (uint32_t)32768;
}

... но, к сожалению, gcc optКажется, что imiser не замечает, что это просто расширение знака младших 16 бит.

0 голосов
/ 10 февраля 2012

Две версии, которые я уже упоминал в комментариях:

#include <stdint.h>

uint32_t sign_extend16_a(uint32_t val)
{
    return (uint32_t)(int16_t)(uint16_t)val;
}

uint32_t sign_extend16_b(uint32_t val)
{
    union { uint16_t u; int16_t i; } ui;
    ui.u = (uint16_t)val;
    return (uint32_t)ui.i;
}

Создает следующий вывод с gcc 4.5.3 на x86-64 с -O1:

.globl sign_extend16_a
    .def    sign_extend16_a;    .scl    2;  .type   32; .endef
sign_extend16_a:
    subq    $8, %rsp
    movswl  %cx, %eax
    addq    $8, %rsp
    ret
.globl sign_extend16_b
    .def    sign_extend16_b;    .scl    2;  .type   32; .endef
sign_extend16_b:
    subq    $8, %rsp
    movswl  %cx, %eax
    addq    $8, %rsp
    ret
0 голосов
/ 10 февраля 2012

Использование объединения:

uint32_t sign_extend16(uint32_t val){
    union{
        uint32_t a;
        int32_t b;
        int16_t c;
    }o;
    o.a=val;
    o.b=o.c;
    return o.a;
}
...