Нет совместимого способа конвертировать подписанные / неподписанные одинакового размера - PullRequest
7 голосов
/ 27 февраля 2012

Боюсь, что я упускаю что-то тривиальное, но, похоже, нет реального безопасного способа преобразования в / из типа со знаком, если вы хотите сохранить исходное значение без знака.

В reinterpret_cast, 5.2.10 не перечисляет преобразование целого числа в целое, поэтому оно не определено (и static_cast не определяет дополнительное преобразование). В интегральных преобразованиях 4.7.3 в основном говорится, что преобразование большого без знака будет определяться реализацией (следовательно, не переносимо).

Это кажется ограничением, так как мы знаем, например, что uint64_t должен на любом оборудовании быть безопасно конвертируемым в int64_t и обратно без изменения значения. Кроме того, правила для стандартных типов макетов фактически гарантируют безопасное преобразование, если бы мы использовали memcpy между двумя типами вместо присвоения.

Я прав? Существует ли законная причина, по которой нельзя reinterpret_cast между целочисленными типами иметь достаточный размер?


Пояснение: Определенно, подписанная версия неподписанного не является гарантированным значением, но я рассматриваю только круговую передачу (unsigned => sign => unsigned)


ОБНОВЛЕНИЕ : При внимательном рассмотрении ответов и перекрестной проверке стандарта я считаю, что на самом деле memcpy не гарантированно работает, поскольку нигде не утверждается, что эти два типа совместимы с макетом, и ни один из типов символов. Дальнейшее обновление, копаясь в C-стандарте, этот memcpy должен работать, так как размер цели достаточно велик, и он копирует байты.


ОТВЕТ : По-видимому, нет технической причины, по которой reinterpret_cast не было разрешено выполнять это преобразование. Для этих целочисленных типов фиксированного размера гарантированно работает memcpy, и действительно, если промежуточное звено может представлять все битовые комбинации, может использоваться любой промежуточный тип (типы с плавающей запятой могут быть опасными, поскольку могут существовать схемы перехвата). В общем случае вы не можете использовать memcpy между любыми стандартными типами макетов, они должны быть совместимы или иметь тип char. Здесь особые черты особые, так как они имеют дополнительные гарантии.

Ответы [ 6 ]

3 голосов
/ 27 февраля 2012

Мы знаем, что вы не можете привести произвольную битовую последовательность к плавающей точке, потому что это может быть представление прерывания.

Существует ли какое-либо правило, согласно которому в подписанном не может быть представления прерыванийцелочисленные типы?(Типы без знака не могут, из-за способа определения диапазона все представления необходимы для допустимых значений)

Подписанные представления также могут включать в себя классы эквивалентности (такие как +0 == -0) и могут приводить значения в такихкласс для канонического представления, таким образом нарушая круговую поездку.

Вот соответствующие правила из Стандарта (раздел 4.7, [conv.integral]):

Если тип назначения не подписан,результирующее значение представляет собой целое число с наименьшим без знака, совпадающее с целым числом источника (по модулю 2 n , где n - количество битов, используемых для представления типа без знака).[Примечание: в представлении дополнения до двух это преобразование является концептуальным, и в битовой комбинации нет изменений (если нет усечения).- примечание конца]

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

Если вы имеете в виду использование reinterpret_cast для указателя или ссылки, а не для значения, вам придется иметь дело с правилом строгого наложения имен .И вы обнаружите, что этот случай явно разрешен .

2 голосов
/ 27 февраля 2012

Как вы указали, memcpy безопасен:

uint64_t a = 1ull<<63;
int64_t b;
memcpy(&b,&a,sizeof a);

Значение b по-прежнему определяется реализацией, поскольку C ++ не требует представления дополнения до двух, , но его преобразование вернет исходное значение.

Как указывает Бо Перссон, int64_t будет дополнением до двух. Следовательно, memcpy должен приводить к значению со знаком, для которого простое целочисленное преобразование обратно в тип без знака хорошо определено как исходное значение без знака.

uint64_t c = b;
assert( a == c );

Кроме того, вы можете реализовать свой собственный метод Sign_cast, чтобы упростить преобразования (я не использую преимущества этих двух дополнений, поскольку они не ограничиваются типами intN_t):

template<typename T>
typename std::enable_if<std::is_integral<T>::value && std::is_signed<T>::value,T>::type
signed_cast(typename std::make_unsigned<T>::type v) {
    T s;
    std::memcpy(&s,&v,sizeof v);
    return s;
}

template<typename T>
typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value,T>::type
signed_cast(typename std::make_signed<T>::type v) {
    T s;
    std::memcpy(&s,&v,sizeof v);
    return s;
}
1 голос
/ 27 февраля 2012

Предположительно, это не разрешено, потому что для машин с представлениями величины знака будет нарушаться принцип наименьшего удивления, когда подписанное 0 сопоставляется с беззнаковым 0, а подписанное -0 сопоставляется с другим (вероятно, очень большим) число.

Учитывая, что существует решение memcpy, я предполагаю, что орган по стандартизации решил не поддерживать такое неинтуитивное отображение, возможно, потому, что unsigned-> Sign-> unsigned не так полезна последовательность, как pointer-> integer-> pointer.

0 голосов
/ 27 февраля 2012

Если я не понимаю вопрос, просто поместите подписанный тип в неподписанный тип и наоборот, чтобы вернуться снова:

#include <iostream>

int main()
{
    signed char s = -128;
    unsigned char u = s;
    signed char back = u;

    std::cout << (int)u << std::endl;
    std::cout << (int)back << std::endl;

    return 0;
}

./a.out 
128
-128
0 голосов
/ 27 февраля 2012

Просто запустите

#include <cstdio>
#include <stdint.h>
using namespace std;

int main()
{
    int64_t a = 5;
    int64_t aa;
    uint64_t b;
    double c;

    b = *reinterpret_cast<uint64_t *>(&a);
    aa = *reinterpret_cast<int64_t *>(&b);

    if (a == aa) {
        printf("as expected, a == aa\n");
    }

    c = *reinterpret_cast<double *>(&a);
    aa = *reinterpret_cast<int64_t *>(&c);

    if (a == aa) {
        printf("again, as expected, a == aa\n");
    }

    printf("what is this I don't even %f.\n", c); // this one should give some undefined behavior here

    return 0;
}

Не удалось вписать его в комментарий.

0 голосов
/ 27 февраля 2012

Проблема в основном в том, что n-битное без знака может не иметь представление в n-битном типе со знаком. Например, 8-разрядное без знака имеет максимальное значение 256, в то время как обязательно 8-разрядное значение со знаком не может иметь значение больше 128 (и обратите внимание, что это не зависит от аппаратной реализации: любое представление будет требуется немного для знака.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...