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