Различает ли между подписанным и unsigned int текущий бит-шаблон переменной в памяти?
Я хочу передать 32-разрядное целое число со знаком x
через сокет. Чтобы получатель знал, какой порядок байтов ожидать, я звоню htonl(x)
перед отправкой. htonl
ожидает a uint32_t
, хотя и я хочу быть уверенным в том, что происходит, когда я накладываю int32_t
на uint32_t
.
int32_t x = something;
uint32_t u = (uint32_t) x;
Всегда ли так, что байты в x
и u
будут одинаковыми? Как насчет отбрасывания:
uint32_t u = something;
int32_t x = (int32_t) u;
Я понимаю, что отрицательные значения передаются в большие значения без знака, но это не имеет значения, так как я просто возвращаюсь на другой конец. Однако, если литые беспорядки с фактическими байтами, то я не могу быть уверен, что отбрасывание вернет то же значение.
Ответы
Ответ 1
В общем, литье в C задается в терминах значений, а не битовых шаблонов - первое будет сохранено (если возможно), но последнее не обязательно так. В случае двух представлений с дополнениями без заполнения - что является обязательным для фиксированных целых типов - это различие не имеет значения, и бросок действительно будет noop.
Но даже если преобразование из подписанного в unsigned изменило бы битовый шаблон, преобразование его обратно снова восстановит исходное значение - с предостережением, что внеконфигурированное преобразование без знака в подпись является реалистичным и может поднять сигнал о переполнении.
Для полной переносимости (которая, вероятно, будет излишней), вам нужно будет использовать функцию punning вместо преобразования. Это можно сделать одним из двух способов:
С помощью указателей указателей, т.е.
uint32_t u = *(uint32_t*)&x;
с которым вы должны быть осторожны, поскольку это может нарушить эффективные правила ввода (но это нормально для вариантов с подписью/без знака целых типов) или через объединения, т.е.
uint32_t u = ((union { int32_t i; uint32_t u; }){ .i = x }).u;
который также может использоваться, например, для преобразования из double
в uint64_t
, который вы не можете выполнять с помощью указателей, если вы хотите избежать поведения undefined.
Ответ 2
В C используются роли, которые означают как "преобразование типов", так и "тип значения". Если у вас есть что-то вроде
(float) 3
Затем это преобразование типа, и фактические биты изменяются. Если вы скажете
(float) 3.0
это тип значения.
Предполагая представление с двумя дополнениями (см. комментарии ниже), при нажатии int
на unsigned int
битовая диаграмма не изменяется, а только ее семантическое значение; если вы вернете его, результат всегда будет правильным. Это относится к случаю неоднозначности типа, поскольку никакие биты не изменяются, только то, как компьютер интерпретирует их.
Заметим, что теоретически 2 дополнения не могут быть использованы, а unsigned
и signed
могут иметь очень разные представления, и в этом случае может измениться фактическая битовая диаграмма.
Однако из C11 (текущий C-стандарт) вам гарантируется, что sizeof(int) == sizeof(unsigned int)
:
(§6.2.5/6) Для каждого из знаковых целых типов существует соответствующий (но другой) неподписанный целочисленный тип (обозначенный ключевое слово unsigned), которое использует то же количество хранения (в том числе знака) и имеет те же требования к выравниванию [...]
Я бы сказал, что на практике вы можете предположить, что это безопасно.
Ответ 3
Это всегда должно быть безопасным, так как типы intXX_t
гарантированы в двух дополнениях , если они существуют:
7.20.1.1. Целые числа точной ширины. Имя typedef intN_t обозначает целочисленный тип со знаком с шириной N, без битов дополнения и двух дополнение. Таким образом, int8_t обозначает такое целое число со знаком тип с шириной ровно 8 бит.
Теоретически, обратное преобразование от uint32_t
до int32_t
определяется реализацией, как и для всех преобразований unsigned
to signed
. Но я не могу себе представить, что платформа будет работать иначе, чем вы ожидаете.
Если вы хотите быть уверенным в этом, вы все равно можете это преобразовать вручную. Вам просто нужно проверить значение для > INT32_MAX
, а затем сделать немного математики. Даже если вы делаете это систематически, достойный компилятор должен уметь обнаруживать это и оптимизировать его.