#c #casting #reinterpret-cast #static-cast #signedness
#c #Кастинг #переинтерпретировать-приведение #статическое приведение #подписанность
Вопрос:
Существует поле данных uint64_t, отправляемое одноранговым узлом связи, оно содержит идентификатор заказа, который мне нужно сохранить в базе данных Postgresql-11, которые НЕ поддерживают целочисленные типы без знака. Хотя реальные данные могут превышать 2 ^ 63, я думаю INT8
, что файл в Postgresql11 может содержать его, если я тщательно выполню кастинг.
Допустим, существует:
uint64_t order_id = 123; // received
int64_t to_db; // to be writed into db
Я планирую использовать один из следующих методов для преобразования значения uint64_t в значение int64_t:
to_db = order_id;
// прямое назначение;to_db = (int64_t)order_id;
// приведение в стиле c;to_db = static_cast<int64_t>(order_id);
to_db = *reinterpret_cast<const int64_t*>( amp;order_id );
и когда мне нужно загрузить его из базы данных, я могу выполнить обратное приведение.
Я знаю, что все они работают, мне просто интересно, какой из них соответствует стандарту C наиболее идеально.
Другими словами, какой метод всегда будет работать на любой 64-битной платформе с любым компилятором?
Спасибо!!!
Комментарии:
1. Каждое из них является неопределенным поведением, если значение превышает 2 ^ 63-1.
2. Существует дополнительная опция:
memcpy(amp;to_db, amp;order_id, 8);
.3. @DanielLangr это работает .. до тех пор, пока система, которая записала эти значения, и система, которая их считывает, находятся в одинаковых условиях
4. @n.’местоимения’m. Более того, AFAIK, 4-й случай всегда приводит к неопределенному поведению.
Ответ №1:
Зависит от того, где он будет скомпилирован и запущен… любой из них не является полностью переносимым без поддержки C 20.
Самый безопасный способ без этого — выполнить преобразование самостоятельно, изменив диапазон значений, что-то вроде этого
int64_t to_db = (order_id > (uint64_t)LLONG_MAX)
? int64_t(order_id - (uint64_t)LLONG_MAX - 1)
: int64_t(order_id ) - LLONG_MIN;
uint64_t from_db = (to_db < 0)
? to_db LLONG_MIN
: uint64_t(to_db) (uint64_t)LLONG_MAX 1;
Если order_id
больше (2 ^ 63 -1), то order_id - (uint64_t)LLONG_MAX - 1
дает неотрицательное значение. Если нет, то приведение к знаку четко определено, и вычитание гарантирует, что значения будут сдвинуты в отрицательный диапазон.
Во время обратного преобразования to_db LLONG_MIN
помещает значение в диапазон [0, ULLONG_MAX].
и делайте обратное при чтении. Используемая вами платформа базы данных или компилятор могут сделать что-то ужасное с двоичным представлением значений без знака при преобразовании их в signed, не говоря уже о том, что существуют разные форматы signed .
По той же причине межплатформенные протоколы часто предполагают использование строкового форматирования или «значения наименьшего бита» для представления значений с плавающей запятой в виде целых чисел, то есть в виде закодированной фиксированной точки.
Ответ №2:
Я бы пошел с memcpy
. Это позволяет избежать (? см. Комментарии) неопределенного поведения, и обычно компиляторы оптимизируют любое копирование байтов:
int64_t uint64_t_to_int64_t(uint64_t u)
{
int64_t i;
memcpy(amp;i, amp;u, sizeof(int64_t));
return i;
}
order_id = uint64_t_to_int64_t(to_db);
GCC -O2
сгенерировал оптимальную сборку для uint64_t_to_int64_t
:
mov rax, rdi
ret
Живая демонстрация: https://godbolt.org/z/Gbvhzh
Комментарии:
1. «Это позволяет избежать неопределенного поведения» Я бы не стал делать такое заявление.
2. @n.’местоимения’m. Почему бы и нет?
3.
memcpy
само по себе все в порядке, проблематичным является последующее использование значения. C 20 может сделать это нормально, не уверен в этом.4. @n.’местоимения’m. Не могли бы вы объяснить, пожалуйста?
5. Я не думаю, что вы можете поместить любой случайный битовый шаблон в целочисленный тип со знаком и ожидать, что он сработает. Я не вижу такой гарантии в стандарте.
Ответ №3:
Все четыре метода будут работать всегда, пока значение находится в пределах диапазона. Первый будет генерировать предупреждения на многих компиляторах, поэтому, вероятно, его не следует использовать. Второй вариант скорее является идиомой C, чем идиомой C , но широко используется в C . Последний уродлив и зависит от тонких деталей стандарта, и его не следует использовать.
Ответ №4:
Эта функция кажется свободной от UB
int64_t fromUnsignedTwosComplement(uint64_t u)
{
if (u <= std::numeric_limits<int64_t>::max()) return static_cast<int64_t>(u);
else return -static_cast<int64_t>(-u);
}
Это сводится к отказу от оптимизации.
Преобразование в другом направлении — это прямое приведение к uint64_t
. Это всегда четко определено.
Комментарии:
1. Не могли бы вы подробнее рассказать о том, что
-u
происходит в последнем приведении?2. @Surt Он вычисляет выражение
-u
по модулю2^64
, что было бы таким же , как2^64-u
если бы мы могли записать константу2^64
.