Подтвердить что ты не робот

Различает ли между подписанным и 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;

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

4b9b3361

Ответ 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, а затем сделать немного математики. Даже если вы делаете это систематически, достойный компилятор должен уметь обнаруживать это и оптимизировать его.