Как преобразовать биты разной длины в массив байтов?

В настоящее время я работаю над проектом, в котором мне нужно реализовать кодирование Элиаса Гаммы для встраиваемой системы (программирование STM32F405 с использованием C). Основным приоритетом является эффективность и скорость кода, поэтому я реализовал таблицу поиска для кодирования Элиаса Гамма, которая содержит значения битов и количество бит для каждого «битового массива» (я использую uint32_t для символов и uint8_t для длин). Однако у меня возникли проблемы с преобразованием этих битов в массив байтов. Ниже приведен пример того, что я пытаюсь сделать.

Для трех символов -> 0b0001001 (длина бит 7), 0b011 (длина бит 3), b000010001 (длина бит 9) мне нужно получить 0b0001001011000010001, таким образом, 0x12 0xC2 0x20 в виде массива байтов для передачи данных через канал связи, такой как USART.

Что меня смущает, так это то, что битовые символы имеют разную длину и процесс «добавления», который необходимо применять к битам на платформе, которая использует байты как наименьшую структуру данных.

Есть ли простой способ сделать это?

Редактировать: Для ясности я пытаюсь добавить 4096 «битовый массив» к X количеству байтов. Число X зависит от общего количества добавленных битов (которое неизвестно во время обработки).

Знакомы ли вы с битовым сдвигом и битовым маскированием? Похоже, вам просто нужно объединить эти различные битовые значения, верно? Если вам нужно передать это через USART, то это должно быть вашим узким местом. Потратить некоторое время на сдвиг битов не должно быть проблемой.

JohnFilleau 22.03.2024 16:21

Всегда ли количество битов будет не более 32?

ikegami 22.03.2024 16:32

Нужно ли помещать байты в память? Если да, то это система с прямым или прямым порядком байтов?

ikegami 22.03.2024 16:33

@ikegami Судя по вопросу, похоже, что биты упакованы в последовательность 8-битных байтов, начиная с наиболее значимого конца каждого исходного поля вниз и с наиболее значимого конца каждого байта назначения вниз, как минимум с нулевыми битами заполнения. значащий конец последнего байта назначения, если требуется.

Ian Abbott 22.03.2024 16:40

Может ли каждый исходный символ быть представлен как одно значение целочисленного типа без знака и длины? Это определенно будет возможно, если длина каждого символа не превышает 64 бита.

Ian Abbott 22.03.2024 16:44

Да, я знаком с битовым сдвигом и битовой маскировкой. Я пытался написать что-нибудь, но оно слишком быстро стало слишком сложным, поэтому я выбросил его. Поэтому я спросил, есть ли простой способ/решение сделать это. @ДжонФилло. Биты хранятся в формате 32 бита, но количество значимых бит является переменным. Например, 0b0001001 хранится как 0b00000000000000000000000000001001, а в другой переменной я храню длину значимых битов, например 7, для 0b0001001. Байты будут передаваться по каналу связи, но до этого они будут храниться в оперативной памяти. STM32F405 имеет прямой порядок байтов.

user19402326 22.03.2024 16:45

@Ian Abbott, это желаемый порядок выходных байтов. Я спросил о порядке байтов в машине, который может быть другим.

ikegami 22.03.2024 16:45

@ikegami В вопросе говорится, что выходные данные представляют собой «массив байтов» (предположительно 8-битные байты). Таким образом, длина вывода в байтах будет равна сумме длин бит плюс 7, разделенной на 8.

Ian Abbott 22.03.2024 16:49

@user19402326 user19402326, ты пропустил один из вопросов. Может ли общее количество объединяемых битов превышать 32? (В вашем примере используется 7+3+9 <= 32, но вы подразумеваете, что это переменная, когда говорите, что у вас есть длины в объектах uint8_t.)

ikegami 22.03.2024 16:53

@Ian Abbott, это не ответ на мой вопрос. Я спросил, какой максимум может дать эта формула. Если массив байтов помещается в регистр, он представляет дополнительные решения.

ikegami 22.03.2024 16:56

Может ли общее количество объединяемых битов превышать 32? Да, они могут. Я пытался применить это к 4096 символам (например, 0b0001001) к байтам. @ikegami

user19402326 22.03.2024 17:04

Это усложняет ситуацию! :) дай мне немного

ikegami 22.03.2024 17:06

Это можно сделать просто (хотя, возможно, не наиболее эффективно), используя функцию бит-бандинга Cortex-M4. Вы просто добавляете биты по одному в битовую область, и они появляются в обычной памяти как массив байтов. Не переносится на устройства, отличные от Cortex-M3/4, но делает код очень простым.

Clifford 22.03.2024 20:28
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
13
144
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Пользователь будет перебирать серию входных битов. Когда «система» имеет достаточно входных данных для генерации байтов, пользователь может их извлечь.

Я представляю себе API, который выглядит так

typedef struct {
    // holds a user-supplied buffer pointer
    char* buf_ptr;
    int buf_size;

    // pointer to usable data within the user-supplied buffer
    char* front;
    int bit_size;
} BitToByteMachine;

// initializes the BitToByteMachine
// The user supplied buffer must be at least large enough to hold the largest
// single input that the user will supply
void bit_to_byte_machine_init(BitToByteMachine*, char* buffer, int buf_size);

// Takes a uint32_t bitfield and a uint8_t num_bits
// Adds the bits into the current machine
// Returns the number of FULL BYTES available for extraction
// Undefined behavior if the buffer is too full to hold the data provided
int bit_to_byte_machine_insert(BitToByteMachine*, uint32_t bitfield, uint8_t num_bits);

// Returns the number of FULL BYTES available
// This can be called when there are still more pending calls to
// bit_to_byte_machine_insert(), for example to start transmitting data
// on a serial line before you've filled up the internal buffer
unsigned char bit_to_byte_machine_full_bytes(BitToByteMachine*);

// Returns the number of FULL OR PARTIAL BYTES available
// This should only be used after all data has been inserted and the only
// thing left to do is finish transmitting the data
unsigned char bit_to_byte_machine_full_or_partial_bytes(BitToByteMachine*);

// Extracts the next available byte
// If the next byte is a FULL BYTE, then will return the FULL BYTE
// If the next byte is a PARTIAL BYTE, then
// it will be zero-padded in the least significant bits
unsigned char bit_to_byte_machine_pop_byte(BitToByteMachine*);

Обычное взаимодействие с пользователем будет выглядеть примерно так

unsigned char my_buffer[4];
BitToByteMachine machine;
bit_to_byte_machine_init(&machine, my_buffer, 4);

while(there_are_codes_to_insert) {
    uint32_t next_code = get_next_code();
    uint8_t next_code_bit_size = get_next_code_bit_size();

    if (bit_to_byte_machine_insert(next_code, next_code_bit_size)) {
        while(bit_to_byte_machine_full_bytes(&machine)) {
            USART_transmit_byte(bit_to_byte_machine_pop_byte(&machine));
        }
    }
}

// no more codes to insert
// send the last bytes with padding
while (bit_to_byte_machine_full_or_partial_bytes(&machine)) {
    USART_transmit_byte(bit_to_byte_machine_pop_byte(&machine));
}

Как этот API работает для вас?

char обычно не следует использовать для битовых операций, поскольку он может быть подписан, и это может вызвать проблемы с битовыми операциями C. Что такое buf_ptr — это начальный адрес буфера, предоставленный пользователем? Если front указывает на «полезные данные», как мы узнаем, где находится конец данных? Почему front не всегда является началом буфера? Это bit_size количество битов в буфере, измеренное от начала буфера или до начала, где front указывает?
Eric Postpischil 22.03.2024 18:07

Как это отвечает на вопрос ОП? Похоже, у них недостаточно знаний о том, как использовать битовые операции для объединения различных строк битов. Предоставление им API якобы для некоторых подпрограмм, которые могли бы это сделать, не говорит им, как реализовать эти подпрограммы.

Eric Postpischil 22.03.2024 18:08
Ответ принят как подходящий

Решение требует:

// Returns false if nothing left.
int get_next_bits( uint8_t *len, uint32_t *bits );

// Sends the first `output_len` bytes pointed by `output`.
send_buffer( uint16_t output_len, const uint8_t *output );

// Allocated memory or array large enough for output.
// See below if this can't be guaranteed.
uint8_t *buf;

Решение:

uint16_t output_len = 0;
uint8_t *output = buf;     // Assumed to be large enough. But see below.
uint8_t available = 0;     // Number of unassigned bits in `*output`.
uint32_t bits;             // Bits to add to output.
uint8_t unassigned;        // Number of bits (left) in `bits`.

while ( get_next_byte( &unassigned, &bits ) ) {
   while ( unassigned > 0 ) {
      if ( available == 0 ) {
         // We need another byte of output.
         // `output` already points to this byte.
         ++output_len;
         *output = 0;
         available = 8;
      }

      if ( available <= unassigned ) {
         // We get here, for example, when we have
         // 5 bits to insert but only 3 bits available in current output byte.
         // So we shift the unassigned bits by 2 to place into position.
         // And we're left with 0 available bits and 2 unassigned bits.
         // We can't forget to "remove" from `bits` the 3 bits we assigned.
         uint8_t shift = unassigned - available;
         *output |= bits >> shift;
         unassigned = shift;
         bits &= ~( ( (uint32_t)0xFFFFFFFF ) << unassigned );
         available = 0;
         ++output;
      } else {
         // We get here, for example, when we have
         // 3 bits to insert and 5 bits available in current output byte.
         // So we shift the unassigned bits by 2 to place into position.
         // And we're left with 2 available bits and 0 unassigned bits.
         uint8_t shift = available - unassigned;
         *output |= bits << shift;
         unassigned = 0;
         available = shift;
      }
   }
}

send_buffer( output_len, output );

При желании случай available == unassigned можно обработать специально.

if ( available == unassigned ) {
   // We get here, for example, when we have
   // 5 bits to insert and 5 bits available in current output byte.
   // We're left with 0 available bits and 0 unassigned bits.
   *output |= bits;
   unassigned = 0;
   available = 0;
   ++output;
}

Если вы не можете гарантировать, что буфер будет достаточно большим, чтобы вместить все биты, замените

if ( available == 0 ) {
   // We need another byte of output.
   // `output` already points to this byte.
   *output = 0;
   available = 8;
   ++output_len;
}

с

if ( available == 0 ) {
   // We need another byte of output.
   // `output` already points to this byte.

   if ( output_len == buf_size ) {
      // Buffer is full, so send and empty it.
      output -= buf_size;
      output_len = 0;
      send_buffer( buf_size, output );
   }

   *output = 0;
   available = 8;
   ++output_len;
}

Код без объяснения не является хорошим ответом. Документирования нескольких переменных недостаточно.

Eric Postpischil 22.03.2024 18:01

@Eric Postpischil, объяснение того, что он делает, можно найти в ОП. Я мог бы скопировать, но смысла в этом нет.

ikegami 22.03.2024 18:04

Опубликованный вопрос может рассказать нам кое-что о желаемом конечном результате, но он не говорит нам, как работает код в этом ответе, и не демонстрирует, как он обеспечивает желаемый конечный результат. Код без объяснения не является хорошим ответом.

Eric Postpischil 22.03.2024 18:05

@Eric Postpischil, он использует смены. Это довольно очевидно. Он должен использовать смены. Поэтому возникает вопрос, как сместиться. Я показал, как переключаться.

ikegami 22.03.2024 18:09

...но добавил еще несколько комментариев.

ikegami 22.03.2024 18:16

Спасибо! Это прекрасно работает. Увидев простоту вашего решения, я понял, насколько я усложнил ситуацию в своем собственном решении. @ikegami

user19402326 22.03.2024 21:39

@ikegami после проверки кода со всем моим набором данных, похоже, возникла небольшая проблема в строке «биты &= 0xFF >> (8 - не назначено);». Под проблемой я имею в виду, что если «не назначено = 23», то этот результат дает отрицательное значение. Таким образом результат становится непредсказуемым. Можете ли вы предложить что-нибудь, чтобы я мог это исправить? Спасибо.

user19402326 25.03.2024 06:15

@ikegami Я изменил эту строку на «bits &= ~(0xFF << (unassigned ));». Теперь, похоже, это работает.

user19402326 25.03.2024 06:44

Должно быть bits &= ~( ( (uint32_t)0xFFFFFFFF ) << unassigned );

ikegami 25.03.2024 15:40

Вот функция, которая копирует значение битового поля указанной ширины до 32 бит в строку назначения байта с прямым порядком байтов. Назначение указывается как указатель на первый байт строки назначения и смещение в битах до позиции, в которой наиболее значимый бит битового поля должен быть сохранен в строке байтов назначения. Сопоставление смещений соответствующим битам в байтах выглядит следующим образом:

  • Смещение 0 — это бит 7 в байте 0.
  • Смещение 1 — это бит 6 в байте 0.
  • Смещение 7 — это бит 0 в байте 0.
  • Смещение 8 — это бит 7 в байте 1.

Функция содержит цикл while, который копирует до 8 бит за раз из исходного битового поля в место назначения. В каждой итерации количество скопированных битов равно минимальному количеству оставшихся нескопированных битов в значении исходного битового поля и количеству битов от текущего смещения назначения (в битах) до следующего кратного 8 битам.

Для удобства функция сохраняет оставшиеся биты исходного битового поля смещенными влево, так что наиболее значимый оставшийся бит находится в битовой позиции 31. Цикл while нормализует позицию назначения, добавляя позицию бита назначения, разделенную на 8, к месту назначения. указатель байта и установка позиции бита назначения в остаток.

#include <stdint.h>
#include <stddef.h>

/**
 * \brief Copy a bit-field into a string of bytes in big-endian order.
 *
 * \param dest Pointer to start of destination byte string.
 * \param dest_ofs_bit Offset in bits to location of most-significant bit
 *        of bit-field in destination byte string.
 * \param srcval Bit-field value as a number.
 * \param srcval_nbits Width of bit-field in bits.
 *
 * The destination byte string buffer is assumed to be at least
 * (\a dest_ofs_bit + \a srcval_nbits + 7) / 8 bytes long.
 */
void set_bitfield_in_bytes(uint8_t *dest, size_t dest_ofs_bit,
        uint32_t srcval, unsigned int srcval_nbits)
{
    /*
     * Move bit-field value to most significant end of srcval for convenience.
     */
    if (srcval_nbits < 32 && srcval_nbits > 0)
    {
        srcval <<= (32 - srcval_nbits);
    }
    while (srcval_nbits > 0)
    {
        unsigned int dest_shift;
        unsigned int nbits;
        uint8_t src_byte;
        uint8_t dest_mask;

        /* Adjust destination byte pointer and offset. */
        dest += dest_ofs_bit / 8;
        dest_ofs_bit %= 8;
        /* Extract up to 8 bits from srcval. */
        src_byte = srcval >> (24 + dest_ofs_bit);
        /* Assume all remaining bits in destination byte will be filled. */
        nbits = 8 - dest_ofs_bit;   /* Number of bits to fill. */
        dest_shift = 0;             /* No left shift. */
        dest_mask = 1u << nbits;    /* Bit-mask will be adjusted below. */
        if (nbits > srcval_nbits)
        {
            /* Not enough bits remaining in source bit-field, so adjust. */
            dest_shift = nbits - srcval_nbits;  /* Left shift. */
            nbits = srcval_nbits;               /* Number of bits to fill. */
        }
        dest_mask -= (1u << dest_shift);    /* Bit-mask of bits to fill. */
        /* Update bits in destination byte. */
        *dest = (*dest & ~dest_mask) | (src_byte & dest_mask);
        /* Shift remaining source bits to most-significant end. */
        srcval <<= nbits;
        /* Account for the number of bits copied in this iteration. */
        dest_ofs_bit += nbits;
        srcval_nbits -= nbits;
    }
}

Обратите внимание, что функция изменяет только указанные биты строки байтов назначения. Если требуется заполнение, вызывающая сторона может либо инициализировать последний байт назначения битами заполнения перед заполнением битовых полей, которые его перезаписывают, либо может вызвать функцию с дополнительным битовым полем, чтобы явно установить заполнение.

Следующий код тестирует функцию с 4 последовательными значениями битовых полей, включая битовое поле заполнения. Значения битовых полей, их ширина и смещения:

  • значение 9, ширина 7, смещение 0
  • значение 3, ширина 3, смещение 7
  • значение 17, ширина 9, смещение 10
  • значение 0 (заполнение), ширина 5, смещение 19

Общая длина составляет 24 бита или 3 байта.

#include <stdint.h>
#include <stdio.h>

void set_bitfield_in_bytes(uint8_t *dest, size_t dest_ofs_bit,
        uint32_t srcval, unsigned int srcval_nbits);

int main(void)
{
    uint8_t bytes[3] = { 0xff, 0xff, 0xff };

    set_bitfield_in_bytes(bytes, 0, 9, 7);
    set_bitfield_in_bytes(bytes, 7, 3, 3);
    set_bitfield_in_bytes(bytes, 7 + 3, 17, 9);
    set_bitfield_in_bytes(bytes, 7 + 3 + 9, 0, 5); /* padding */
    printf("%02x %02x %02x\n", bytes[0], bytes[1], bytes[2]);
}

Выход:

12 c2 20

Функцию по-прежнему можно использовать в сценарии, где буфер назначения имеет фиксированный размер, общая длина исходных битовых полей больше, чем размер буфера в битах (размер буфера в байтах, умноженный на 8), а буфер назначения равен периодически сбрасывается на какое-либо устройство вывода. Вызывающей стороне просто нужно быть осторожным, чтобы не переполнить буфер и не отправить частично заполненные байты на устройство вывода. Если буфер назначения является циклическим буфером, ему просто нужно 4 байта (32 бита) дополнительного пространства после конца циклического буфера, чтобы справиться с битовыми полями, которые записывают за конец буфера, а затем он может переместить избыточное пространство. байтов в начало буфера и отрегулируйте смещение места назначения.

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