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

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

В первом примере нет проблем с использованием битового сдвига для преобразования порядка байтов для типа 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 с G CC 8.3.0.

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

Ответы [ 2 ]

1 голос
/ 28 февраля 2020

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

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

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

0 голосов
/ 28 февраля 2020

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

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

...