Является ли приведение int32 к uint32 запретом? - PullRequest
4 голосов
/ 05 января 2020

Я хочу вставить биты int32_t в тип uint32_t без каких-либо преобразований, просто переосмысление. Следующий код выполняет именно то, что я хочу:

int32_t  iA = -1;
uint32_t uA = *(uint32_t*)&iA;

Но мне было интересно, могу ли я рассчитывать на следующее, что проще написать приведение, генерирующее ту же (или меньшую) сборку, в идеале просто mov с? (то есть, он никогда не будет делать с ним "математику", оставляя основные биты нетронутыми.)

int32_t  iB = -1;
uint32_t uB = (uint32_t)iB;

assert(uA == uB); // ?

Ответы [ 2 ]

7 голосов
/ 05 января 2020

До C ++ 20 представление целых чисел со знаком определяется реализацией. Тем не менее, std::intX_t гарантированно будет иметь представление с 2-ым дополнением даже до C ++ 20:

int8_t, int16_t, int32_t, int64_t - целочисленный тип со знаком со шириной ровно 8, 16, 32 и 64 бита соответственно без битов заполнения и использования дополнения 2 для отрицательных значений (предоставляется только в том случае, если реализация непосредственно поддерживает тип)

Когда вы пишете

std::int32_t  iA = -1;
std::uint32_t uA = *(std::uint32_t*)&iA;

, вы получаете значение со всеми установленными битами. Стандарт говорит , что доступ к std::int32_t через указатель типа std::uint32_t* разрешен, если "тип похож на ... тип, который является типом со знаком или без знака, соответствующим типу Dynami c объекта ". Таким образом, строго говоря, мы должны убедиться, что std::uint32_t действительно является беззнаковым типом, соответствующим std::int32_t, перед разыменованием указателя:

static_assert(std::is_same_v<std::make_unsigned_t<std::int32_t>, std::uint32_t>);

Когда вы пишете

std::int32_t  iB = -1;
std::uint32_t uB = (std::uint32_t)iB;

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

Что касается сборки, то оба приведения не выполняются:

std::uint32_t foo() {
    std::int32_t  iA = -1;
    static_assert(std::is_same_v<std::make_unsigned_t<std::int32_t>, std::uint32_t>);
    return *(std::uint32_t*)&iA;
}

std::uint32_t bar() {
    std::int32_t  iB = -1;
    return (std::uint32_t)iB;
}

результат :

foo():
        mov     eax, -1
        ret
bar():
        mov     eax, -1
        ret
3 голосов
/ 05 января 2020

Использование memcpy является распространенным решением, позволяющим избежать неопределенного поведения при наложении типов. В комментариях было указано, что псевдонимы типов, которые отличаются только по своей подписи, это нормально, но это не будет, например, с float и int.

memcpy работает до тех пор, пока представление объекта допустимо для типа.

Компиляторы очень хороши в оптимизации memcpy вызовов, в этом случае вызов полностью оптимизирован вне .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...