25
Апр
2018

Как правильно положить в буфер число размером более чем 1 байт?

У нас есть массив байтов (допустим, пакет, который мы хотим отправить через сеть) и нам надо в определённое место этого массива положить 32-битное число. Используем порядок байтов little endian (т.е. "обычный").

Напрашиваются два варианта решения:

Первый вариант:

inline void put_uint32_le(unsigned char *dst, uint32_t n)
{
    *reinterpret_cast<uint32_t *> (dst) = n;
}

Второй вариант:

inline void put_uint32_le(unsigned char *dst, uint32_t n)
{
    dst[0] = static_cast<unsigned char> (n & 255);
    dst[1] = static_cast<unsigned char> ((n >> 8) & 255);
    dst[2] = static_cast<unsigned char> ((n >> 16) & 255);
    dst[3] = static_cast<unsigned char> (n >> 24);
}

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

Лично мне неприятно так наплевательски относиться к быстродействию функции, которая у меня будет выполняться тысячу раз в секунду.

Первый вариант работает быстрее, но он работает только если у нас машина с little endian byte order. Но это ладно. Можно сделать так:

inline void put_uint32_le(unsigned char *dst, uint32_t n)
{
#ifdef WE_HAVE_LITTLE_ENDIAN_PROCESSOR
    *reinterpret_cast<uint32_t *> (dst) = n;
#else
    dst[0] = static_cast<unsigned char> (n & 255);
    dst[1] = static_cast<unsigned char> ((n >> 8) & 255);
    dst[2] = static_cast<unsigned char> ((n >> 16) & 255);
    dst[3] = static_cast<unsigned char> (n >> 24);
#endif
}

Хуже другое. Указатель 'dst' может быть невыровненным. В стандарте написано, что выравнивание это "a number of bytes between successive addresses at which a given object can be allocated". Во всём стандарте выравнивание рассматривается именно в таком ключе - ни одно место в стандарте не говорит о том, что к многобайтному числу можно обратиться по невыровненному указателю.

Более того, я и в документации на компилятор ("man gcc", "info gcc") не нахожу, где бы было написано, что компилятор это поддерживает хотя-бы на intel-архитектуре, хотя-бы в качестве расширения стандарта! Хоть-бы #ifdef'ы какие-нибудь сделать, но как именно их сделать, не могу понять!

То есть, то, что я делаю в первом варианте, приводит к undefined behavior. Да, я так уже делал в сотнях программ, и на intel-совместимых процессорах всегда работало. Да, так делаю не только я один. Но никто не обещал, что в следующей версии компилятора это не заглючит, поэтому так делать нельзя.

Более того, если прищуриться, становится видно, что и второй вариант не абсолютно портируемый. Во-первых, у нас может не быть типа uint32_t, поэтому вместо него надо использовать uint_fast32_t. Во-вторых, у нас, теоретически, unsigned char может состоять более чем из восьми битов. Если первая проблема легко решается, то со второй ума не приложу что делать.

Кто что посоветует?


VTT:

Что-то вы напридумывали про длинный ассемблер и укладку в стек...

А если подбавить реализьму?

Попробуйте с нижеследующим кодом. Только обязательно укажите версию GCC не позднее 6.3.

#include <string.h>
#include <stdint.h>

inline uint16_t get_uint16_le(const unsigned char *src)
{
    return *reinterpret_cast<const uint16_t *> (src);
}

inline void put_uint32_le(unsigned char *dst, uint_fast32_t n)
{
    memcpy(dst, &n, 4);
//    *reinterpret_cast<uint32_t *> (dst) = n;
}

extern const uint32_t crc32_table[256];

static uint32_t calculate_crc32(const unsigned char *buf, size_t len)
{
    uint32_t crc = 0;

    while (len--)
    {
        crc ^= crc32_table[*(buf++)];
    }

    return crc;
}

void set_crc32(unsigned char *begin)
{
    size_t sz = 3 + get_uint16_le(begin);
    put_uint32_le(begin + sz, calculate_crc32(begin, sz));
}

Источник: https://ru.stackoverflow.com/questions/819086/%D0%9A%D0%B0%D0%BA-%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE-%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C-%D0%B2-%D0%B1%D1%83%D1%84%D0%B5%D1%80-%D1%87%D0%B8%D1%81%D0%BB%D0%BE-%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%D0%BE%D0%BC-%D0%B1%D0%BE%D0%BB%D0%B5%D0%B5-%D1%87%D0%B5%D0%BC-1-%D0%B1%D0%B0%D0%B9%D1%82

Тебе может это понравится...

Добавить комментарий