Как можно безопасно static_cast между unsigned int и int?
У меня 8 символов string
представляющий шестнадцатеричное число, и мне нужно преобразовать его в int
, Это преобразование должно сохранить битовую комбинацию для строк "80000000"
и выше, т. е. эти цифры должны быть отрицательными. К сожалению, наивное решение:
int hex_str_to_int(const string hexStr)
{
stringstream strm;
strm << hex << hexStr;
unsigned int val = 0;
strm >> val;
return static_cast<int>(val);
}
не работает для моего компилятора, если val > MAX_INT
(возвращаемое значение равно 0). Изменение типа val на int
также приводит к 0 для больших чисел. Я попробовал несколько разных решений из разных ответов здесь на SO, но пока не увенчался успехом.
Вот что я знаю:
- Я использую компилятор HP C++ в OpenVMS (я думаю, что использую процессор Itanium).
sizeof(int)
будет не менее 4 на каждую архитектуру, на которой будет выполняться мой код.- Преобразование из числа> INT_MAX в int определяется реализацией. На моей машине это обычно приводит к 0, но интересно
long
вint
результаты вINT_MAX
когда значение слишком велико.
Это на удивление трудно сделать правильно, или, по крайней мере, для меня. Кто-нибудь знает портативное решение для этого?
Обновить:
изменения static_cast
в reinterpret_cast
приводит к ошибке компилятора. Комментарий побудил меня попробовать бросок в стиле C: return (int)val
в коде выше, и это сработало. На этой машине. Будет ли это все еще безопасно на других архитектурах?
4 ответа
Хотя есть способы сделать это, используя приведение и преобразование, большинство полагается на неопределенное поведение, которое может иметь четко определенное поведение на некоторых машинах / с некоторыми компиляторами. Вместо того, чтобы полагаться на неопределенное поведение, скопируйте данные:
int signed_val;
std::memcpy (signed_val, val, sizeof(int));
return signed_val;
Цитируя стандарт C++03, §4.7/3 (Интегральные преобразования):
Если тип назначения подписан, значение не изменяется, если оно может быть представлено в типе назначения (и ширине битового поля); в противном случае значение определяется реализацией.
Поскольку результат определяется реализацией, по определению невозможно найти действительно переносимое решение.
Вы можете отменить число без дополнения до двух без знака, взяв дополнение и добавив его. Итак, давайте сделаем это для негативов:
if (val < 0x80000000) // positive values need no conversion
return val;
if (val == 0x80000000) // Complement-and-addition will overflow, so special case this
return -0x80000000; // aka INT_MIN
else
return -(int)(~val + 1);
Это предполагает, что ваши целые числа представлены с 32-битным представлением с двойным дополнением (или имеют аналогичный диапазон). Он не зависит от какого-либо неопределенного поведения, связанного с целочисленным переполнением со знаком (обратите внимание, что поведение целочисленного переполнения без знака хорошо определено - хотя и здесь этого не должно происходить!).
Обратите внимание, что если ваши целые числа не 32-битные, все становится сложнее. Вам может понадобиться что-то вроде ~(~0U >> 1)
вместо 0x80000000
, Кроме того, если ваши целые числа не дополняются двумя, у вас могут возникнуть проблемы с переполнением некоторых значений (например, на машине с одним дополнением, -0x80000000
не может быть представлен в 32-битном целом со знаком). Тем не менее, машины без двойного дополнения сегодня очень редки, так что это вряд ли будет проблемой.
Вот еще одно решение, которое сработало для меня:
if (val <= INT_MAX) {
return static_cast<int>(val);
}
else {
int ret = static_cast<int>(val & ~INT_MIN);
return ret | INT_MIN;
}
Если я маскирую старший бит, я избегаю переполнения при касте. Тогда я смогу ИЛИ вернуть его благополучно.
В C++20 будет std::bit_cast, дословно копирующий биты:
#include <bit>
#include <cassert>
#include <iostream>
int main()
{
int i = -42;
auto u = std::bit_cast<unsigned>(i);
// Prints 4294967254 on two's compliment platforms where int is 32 bits
std::cout << u << "\n";
auto roundtripped = std::bit_cast<int>(u);
assert(roundtripped == i);
std::cout << roundtripped << "\n"; // Prints -42
return 0;
}
cppreference показывает пример того, как можно реализовать свои собственныеbit_cast
с точки зрения memcpy
(в разделе "Примечания").
Хотя OpenVMS вряд ли получит поддержку C++20 в ближайшее время, я надеюсь, что этот ответ поможет кому-то найти тот же вопрос через поиск в Интернете.
unsigned int u = ~0U;
int s = *reinterpret_cast<int*>(&u); // -1
Противоположно:
int s = -1;
unsigned int u = *reinterpret_cast<unsigned int*>(&s); // all ones