До 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