Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is casting int32 to uint32 a no-op?

I want to stick the bits of an int32_t into the type uint32_t without any transformation, just a reinterpretation. The following code does precisely what I want:

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

But I was wondering, can I rely on the following easier to write cast generating the same (or less) assembly, ideally just movs? (i.e., it'll never do "math" to it, leaving the underlying bits untouched.)

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

assert(uA == uB); // ?
like image 994
Anne Quinn Avatar asked Dec 23 '22 19:12

Anne Quinn


1 Answers

Until C++20, the representation of signed integers is implementation-defined. However, std::intX_t are guaranteed to have 2s'-complement representation even before C++20:

int8_t, int16_t, int32_t, int64_t - signed integer type with width of exactly 8, 16, 32 and 64 bits respectively with no padding bits and using 2's complement for negative values (provided only if the implementation directly supports the type)

When you write

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

you get the value with all bits set. The standard says that accessing std::int32_t through a pointer of type std::uint32_t* is permitted if "type is similar to ... a type that is the signed or unsigned type corresponding to the dynamic type of the object". Thus, strictly speaking, we have to ensure that std::uint32_t is indeed an unsigned type corresponding to std::int32_t before dereferencing the pointer:

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

When you write

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

you rely on the conversion into the unsigned type that is well-defined and is guaranteed to produce the same value.

As for the assembly, both casts are no-ops:

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;
}

result in:

foo():
        mov     eax, -1
        ret
bar():
        mov     eax, -1
        ret
like image 164
Evg Avatar answered Dec 26 '22 00:12

Evg