Проблема преобразования порядка байтов для беззнакового 64-битного числа в C

Я играю с преобразованием с прямым порядком байтов/обратным порядком байтов и нашел кое-что, что немного сбивает с толку, но также интересно.

В первом примере нет проблем с использованием сдвига битов для преобразования порядка байтов для типа uint32_t. По сути, он приводит целое число uint32_t к массиву uint8_t и пытается получить доступ к каждому байту и сдвигу битов.

Пример №1:

uint32_t htonl(uint32_t x)
{
    uint8_t *s = (uint8_t*)&x;
    return (uint32_t)(s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3]);
}

Однако, если я попытаюсь сделать что-то подобное для uint64_t ниже, компилятор выдаст предупреждение о том, что ширина [0] меньше 56 бит, как в примере № 2 ниже.

Пример №2:

uint64_t htonl(uint64_t x)
{
    uint8_t *s = (uint8_t*)&x;
    return (uint64_t)(s[0] << 56 ......);
}

Чтобы заставить его работать, я должен извлечь каждый байт в uint64_t, чтобы я мог выполнять битовый сдвиг без каких-либо ошибок, как в примере № 3 ниже.

Пример №3:

uint64_t htonll2(uint64_t x)
{
    uint64_t byte1 = x & 0xff00000000000000;
    uint64_t byte2 = x & 0x00ff000000000000;         
    uint64_t byte3 = x & 0x0000ff0000000000;
    uint64_t byte4 = x & 0x000000ff00000000;
    uint64_t byte5 = x & 0x00000000ff000000;
    uint64_t byte6 = x & 0x0000000000ff0000;                                                                                              
    uint64_t byte7 = x & 0x000000000000ff00;
    uint64_t byte8 = x & 0x00000000000000ff;

    return (uint64_t)(byte1 >> 56 | byte2 >> 40 | byte3 >> 24 | byte4 >> 8 |
                      byte5 << 8  | byte6 << 24 | byte7 << 40 | byte8 << 56);
}

Меня немного смущают Example #1 и Example #2, насколько я понимаю, оба s[i] имеют размер uint8_t, но почему-то если сдвигается только на 32 бита или меньше, то вообще нет проблем, но есть проблема при сдвиге вроде 56 биты. Я запускаю эту программу на Ubuntu с GCC 8.3.0.

В этом случае компилятор неявно преобразует s[i] в 32-битные числа? sizeof(s[0]) равно 1, когда я добавил к этому отладочные сообщения.


person keye    schedule 28.02.2020    source источник


Ответы (2)


Выражение s[0] имеет 8-битный целочисленный тип, который преобразуется в 32-битное целое число без знака при работе с оператором сдвига, поэтому s[0] << 24 в первом примере работает нормально, поскольку сдвиг на 24 не превышает длину uint. .

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

person CiaPan    schedule 28.02.2020
comment
uint8_t будет повышен до int. Он будет подписанным, а не беззнаковым, и это будет любая ширина int, которая в наши дни обычно составляет 32 бита, но стандарт C разрешает 16 бит или более. Это не будет работать «ОК», потому что, если старший бит uint8_t включен, а int равен 32 битам, сдвиг на 24 бита выйдет за пределы диапазона значений, представляемых в int, а поведение не определено стандартом C. - person Eric Postpischil; 28.02.2020

Значения с типом меньше int повышаются до int при использовании в выражении. Предполагая, что int является 32-битным на вашей платформе, это работает в большинстве случаев при преобразовании 32-битного значения. Время, когда это не сработает, - это если вы сдвинете 1 бит в бит знака.

В 64-битном случае это означает, что вы пытаетесь сдвинуть значение больше, чем его длина в битах, что является неопределенным поведением.

Вам нужно привести каждый байт к uint64_t в обоих случаях, чтобы сдвиги работали правильно.

person dbush    schedule 28.02.2020