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

Другие вопросы по тегам