Compile time C++ checksum

222
29 ноября 2021, 02:40

Есть структура:

#pragma pack(push, 1)
struct protocol_header {
    uint32_t sign = 42;
    uint8_t checksum = 0;
    uint8_t length = 6;
    constexpr protocol_header(void) {
        checksum += sign & 0xFF000000 >> 24;
        checksum += sign & 0x00FF0000 >> 16;
        checksum += sign & 0x0000FF00 >> 8;
        checksum += sign & 0x000000FF >> 0;    
        m_checksum += length;
        m_checksum = 0x100 - m_checksum;
    }
};
#pragma pack(pop)

и объявление может быть таким. Главное чтобы заголовок попал в нужную секцию!

__attribute__((section(".protocol_header_section"))
static protocol_header;

Требуется во время компиляции занести в поле checksum побайтовую сумму этой структуры.

"Плохое" решение приведено.

Размер реальной структуры больше. Поэтому складывать байты вручную не получится.

Answer 1

Если нужны вычисления именно при компиляции, то можно это сделать несколькими вариантами.

Через #define-макрос влоб (если структура одна)

#pragma pack(push, 1)
#define protocol_header_sign 42
#define protocol_header_len 6
struct protocol_header { 
  uint32_t sign = protocol_header_sign ;
  uint8_t checksum = (protocol_header_sign  & 0xFF) +   protocol_header_len  /*+И так далее*/;
  uint8_t length = protocol_header_len;
};
 #pragma pack(pop)

Так же можно сделать шаблонный #define (#define PROTOCOL_HEADER(SIGN,LEN) struct{...) но шаблон template практичнее.

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

   #pragma pack(push, 1)
   template < uint32_t SIGN,uint8_t LEN> struct protocol_header  {
     uint32_t sign = SIGN;
     uint8_t checksum =  (SIGN & 0xFF) + LEN  /*+ и так далее*/;
     uint8_t length = LEN;
    };
   #pragma pack(pop)
   protocol_header<42,6> myprotocol1;

Только тогда в полях будут константные выражения, и расчёт будет сделан компилятором. Переменные в константые выражения подставлять нельзя (и в protocol_header<42,6> тоже нужно подставлять строго константы).

Ну и на десерт, вариант с пред-инициализацией структуры, в отдельных случаях подходит. Думаю это самый практичный вариант. В отличии от предыдущих двух - не будет плодится кучи копий класса. protocol_header mystruct={ 42, выражение, 6 }, что можно завернуть в #define макрос, который так же можно использовать повторно.

 #define init_protocol_header (SIGN,LEN) {SIGN,(SIGN & 0xFF) + LEN  /*+ и так далее*/, LEN }
 protocol_header  mystruct = init_protocol_header (42,6);

В итоге компилятор инициализирует переменную в mystruct, и либо будет использовать push/memcpy для инициализации в локальной области, либо забьет посчитанные константы в BSS, при обьявлении в глобальной области.

Answer 2

Если имена полей нельзя терять, то самый простой вариант - добавить constexpr функции:

// предется вручную прописывать для всех используемых типов, кроме uint8_t ...
constexpr uint8_t  calc_hi(uint16_t i) { return static_cast<uint8_t >( (i>>8)  ); }
constexpr uint16_t calc_hi(uint32_t i) { return static_cast<uint16_t>( (i>>16) ); }
constexpr uint32_t calc_hi(uint64_t i) { return static_cast<uint32_t>( (i>>32) ); }
constexpr uint8_t  calc_low(uint16_t i) { return static_cast<uint8_t >( i ); }
constexpr uint16_t calc_low(uint32_t i) { return static_cast<uint16_t>( i ); }
constexpr uint32_t calc_low(uint64_t i) { return static_cast<uint32_t>( i ); }

constexpr uint8_t calc_checksumm(uint8_t  i) { return i; }
template<typename T>
constexpr uint8_t calc_checksumm(T i)
{
    return calc_checksumm( calc_hi(i) ) + calc_checksumm( calc_low(i) ) ;
}
template<typename A0, typename ... Args>
constexpr uint8_t calc_checksumm(A0 a0, Args...args)
{
    return calc_checksumm(a0)+calc_checksumm(args...);
}
template<typename T>
constexpr uint8_t calc_len(T ) { return sizeof(T); }
template<typename A0, typename ... Args>
constexpr uint8_t calc_len(A0 a0, Args...args)
{
    return calc_len(a0)+calc_len(args...);
}

struct protocol_header {
    uint32_t sign  = 42;
    uint64_t sign2 = 24;
    // Типы Важны! Но можно убрать списки в один define.
    uint8_t checksum = calc_checksumm((uint32_t) 42, uint64_t(24))
                      +calc_len      ((uint32_t) 42, uint64_t(24));
    uint8_t length   = calc_len      ((uint32_t) 42, uint64_t(24));
};

Здесь есть существенный недостаток - список типов и значений повторяется четыре раза. Можно слегка сгладить этот недостаток define-ами.

READ ALSO
Проблема с отображением текстур

Проблема с отображением текстур

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

261
Ошибка при переписывание кода в функции

Ошибка при переписывание кода в функции

Попалась вот такая задача:

129
Создать класс по обработке массива данных

Создать класс по обработке массива данных

ООП в плюсах для меня тема новаяНужно написать класс my_sample который должен иметь среди своих приватных полей вектор long double, который назвать...

104
Спецификатор времени компиляции noexcept

Спецификатор времени компиляции noexcept

А для чего нужны спецификации исключения noexcept(true) и noexcept(false), как и когда они используются?

96