2012-02-27 8 views
7

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

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

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

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


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


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


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

+0

Я думаю, что вы всегда можете это сделать, ведь с помощью reinterpret_cast вы просто говорите компилятору, как интерпретировать местоположение памяти, не изменяя значение местоположения. –

+0

«на любом оборудовании». Именно в этом и есть смысл. Возможно, на любом оборудовании, с которым вы могли бы работать, но C++ не предназначен для академических целей для поддержки некоторых других вещей, кроме дополнения 2-го, а потому, что на самом деле такое оборудование. И (u) типы intXX_t просто обязаны вести себя в вычислениях «как-если» они были дополнением к 2-м, нет требования, что аппаратное обеспечение должно быть. – PlasmaHH

+0

@PlasmaHH, я хочу сказать, что 'uint64_t',' int64_t' - это точный размер (если поддерживается на оборудовании), и, таким образом, гарантируется, что «memcpy» будет конвертировать (по правилам стандартных типов макета). Мне все равно, что здесь есть 2 дополнения, я хочу конвертировать назад и вперед. –

ответ

2

Как вы отмечаете, тетср безопасен:

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

Значения б еще определяется реализация, так как C++ не требует двоичного дополнение представления, но преобразование его обратно даст вам исходное значение ,

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

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

Кроме того, вы можете реализовать свой собственный «signed_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; 
} 
+0

Любое понимание того, почему 'reinterpret_cast' не разрешает это преобразование? –

+1

Если 'int64_t' определен, он должен быть двухкомпонентным 64-битным дополнением, без битов заполнения. Поэтому, если определен 'int64_t',' memcpy' должен работать, и я никогда не слышал о случае, когда простой актер не работал (хотя это формальная реализация определена). Для стандартных интегральных типов, таких как 'int' или' long', он менее уверен, так как количество бит и подписанное представление определены реализацией (и если реализация не является дополнением к двум, могут произойти странные вещи). –

+0

@JamesKanze Вы уверены, что 'int64_t' должен быть дополнением? –

0

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

+0

Ах, но это все, правила для стандартных типов макетов _guarantee_, которые вы можете конвертировать взад и вперед без потери стоимости. То есть, не имеет значения, какое логическое значение хранится в подписанной переменной, используя гарантии memcpy, вы можете вернуть исходное значение без знака (так как тип является стандартным макетом и имеет достаточный размер). –

+3

Максимальные значения - 255 и 127. Просто говорите. : P – TheBuzzSaw

-1

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

#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

Это не гарантирует работу, и может быть безопасным и по-прежнему соответствовать стандартам. 'reinterpret_cast' может использоваться для обратного прохода, но вы не можете использовать промежуточное звено как реальный указатель, так как он определяется реализацией. _I не знаю какого-либо оборудования/компилятора, где это не работает. –

+0

Скорее всего, переменная c не содержит допустимую форму IEEE 754. С этого момента ничего не гарантировано. То же самое касается подписанного/неподписанного, поскольку C++ ничего не говорит о том, как целые числа должны быть упакованы. – foxx1337

+0

Кроме того, это нарушает строгое правило псевдонимов (сглаживание целого числа как «double» и наоборот). –

1

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

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

+1

Игнорируйте эти типы размеров на мгновение. Я бы сказал, что отображение из 'intptr_t' в/из' uintptr_t' было бы полезной последовательностью. Просто подумайте, пытаетесь ли вы объединиться с API-интерфейсами, которые только что выбрали различную подпись. –

-1

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

#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 
+1

Вопрос о гарантиях, предоставляемых стандартом. Все в полной мере ожидают, что туда и обратно придется работать на общем оборудовании, но стандарт на самом деле не гарантирует его. Это удивительно, так как если вы делаете это с помощью 'memcpy', то туда и обратно гарантируется *. –

+0

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

3

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

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

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

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

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

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

Если вы имеете в виду, используя reinterpret_cast на указатель или ссылку, а не значение, вы должны иметь дело с the strict-aliasing rule. И вы найдете то, что this case is expressly allowed.

+0

Хм, по этой логике вы также не сможете «memcpy» использовать значение с плавающей запятой, так как оно может ловушку. Возможно ли, что memmmpi между подписанными/неподписанными целыми типами фактически не гарантируется? –

+0

@ edA-qamort-ora-y: 'memcpy' никогда не будет ловушкой, потому что он не интерпретирует свои аргументы. Но использование результирующего значения может быть проблемой. –

+0

Согласны ли вы с тем, что выполнение обратной связи через 'memcpy' гарантированно сохранит исходное значение? –