Когда приведение изменяет биты значения в C++?

У меня есть C++ unsigned int который на самом деле хранит значение со знаком. Я хочу привести эту переменную к signed int, так что значения без знака и со знаком имеют одинаковое двоичное значение.

unsigned int lUnsigned = 0x80000001;
int lSigned1 = (int)lUnsigned;                   // Does lSigned == 0x80000001?
int lSigned2 = static_cast<int>(lUnsigned);      // Does lSigned == 0x80000001?
int lSigned3 = reinterpret_cast<int>(lUnsigned); // Compiler didn't like this

Когда преобразования приводят к изменению битов переменной в C++? Например, я знаю, что кастинг из int к float изменит биты, потому что int является дополнением до двух float является плавающей точкой. Но как насчет других сценариев? Я не ясно о правилах для этого в C++.

В разделе 6.3.1.3 спецификации C99 говорится, что приведение целого числа без знака к целому числу со знаком определяется компилятором!

7 ответов

Решение

Преобразование типов может

  • сохранить концептуальное значение (возможно, придется изменить битовый шаблон), или

  • сохранить битовый шаблон (концептуальное значение, возможно, придется изменить).

Единственный C++ бросок, который гарантированно всегда сохраняет битовый паттерн, это const_cast,

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

dynamic_cast обычно изменяет как битовый шаблон, так и значение, так как он обычно копается в объект и возвращает указатель / ссылку на подобъект запрошенного типа.

static_cast может изменить битовый шаблон как для целых чисел, так и для указателей, но почти все существующие компьютеры используют представление целых чисел со знаком (называемое дополнением к двум), где static_cast не будет менять битовый паттерн. Что касается указателей, достаточно сказать, что, например, когда базовый класс неполиморфен, а производный класс полиморфен, используя static_cast переход от указателя к производному к указателю на базу или наоборот, может изменить битовый шаблон (как вы можете видеть при сравнении void* указатели). Теперь целые числа...

С n битами значения целочисленный тип без знака имеет 2 ^ n значений в диапазоне от 0 до 2 ^ n -1 (включительно).

Стандарт C++ гарантирует, что любой результат типа будет помещен в этот диапазон путем сложения или вычитания подходящего кратного 2 ^ n.

На самом деле, именно так это описывает стандарт С; стандарт C++ просто говорит, что операции выполняются по модулю 2 ^ n, что означает то же самое.

С формой дополнения до двух значение со знаком - x имеет тот же битовый шаблон, что и значение без знака - x + 2 ^ n. То есть тот же битовый шаблон, что и в стандарте C++, гарантирует, что вы получите преобразование -x в тип без знака того же размера. Это простые основы формы дополнения двоих, что это именно та гарантия, которую вы ищете.:-)

И почти все существующие компьютеры используют форму дополнения до двух.

Следовательно, на практике вам гарантирован неизменный битовый шаблон для ваших примеров.

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

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

Вы вроде спрашиваете разницу между static_cast а также reinterpret_cast,

Если ваша реализация использует дополнение 2 для целочисленных типов со знаком, то приведение целочисленных типов со знаком к знаковым целым типам одинаковой ширины не изменяет битовую комбинацию.

Преобразование из неподписанного в подписанное теоретически может делать все что угодно, когда значение находится вне диапазона подписанного типа, поскольку оно определяется реализацией. Но очевидная вещь для реализации дополнения 2 состоит в том, чтобы использовать тот же самый битовый образец.

Если ваша реализация не использует дополнение 2, то приведение между знаковыми и беззнаковыми значениями изменит битовую комбинацию, когда вовлеченное значение со знаком будет отрицательным. Однако такие реализации встречаются редко (я не знаю, как использовать компилятор, отличный от 2, в компиляторах C++).

Используя приведение в стиле C или static_cast, чтобы бросить unsigned int к signed int может все же позволить компилятору назначить первый последнему напрямую, как если бы приведение не было выполнено, и, таким образом, может изменить биты, если unsigned int значение больше, чем то, что signed int может держать. reinterpret_cast должно работать, или вы можете использовать приведение типа с помощью указателя:

unsigned int lUnsigned = 0x80000001; 
int lSigned1 = *((int*)&lUnsigned);
int lSigned2 = *(reinterpret_cast<int*>(&lUnsigned));

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

Ты ищешь int lSigned = reinterpret_cast<int&>(lUnsigned);

Вы не хотите переосмысливать значение lUnsigned, вы хотите переосмыслить объект lUnsigned. Следовательно, приведение к ссылочному типу.

Приведение - это просто способ переопределить средство проверки типов, оно не должно изменять сами биты.

Другие вопросы по тегам