Как можно безопасно 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
Другие вопросы по тегам