Битовый буфер не ведет себя должным образом в C

Мне нужно записать отдельные биты в файл (для кода Хаффмана). Для этого я отправляю биты в функцию, которая буферизует их до тех пор, пока не будет заполнен байт, а затем возвращает байт. Я не могу понять, почему это не работает, функция выводит неправильные байты (но ошибок нет). Вот функция bitsBuffer:

// Buffers bits until one byte is filled, the returns it and return code turns positive
int bitsBuffer(char inByte, char nBits, char* outByte, char finish)
{
    int i;
    static short unsigned buffer = 0;
    static char unsigned offset = 0;

    // Very readable way to add nBits Bits to buffer
    buffer |= (inByte & ~(~0 << nBits)) << offset;
    offset += nBits;
    if (offset >= 8) {
        *outByte = (char)buffer;
        buffer >>= 8;
        offset -= 8;
        return 1;
    }
    if (finish) {
        buffer = 0;
        if (offset) {
            *outByte = (char)buffer;
            offset = 0;
            return 1;
        }
        offset = 0;
        return 0;
    }
    return 0;
}

Я использую эту программу для проверки битбуфера, а затем передаю вывод в xxd -b для просмотра битов:

#include "bitsHandler.h"
#include <stdio.h>

int main()
{
    char a[] = { 0b0110, 0b1, 0b100010, 0b111, 0b100110, 0b0 };
    char b[] = { 4, 1, 6, 3, 6, 1 };
    char c[100];
    int counter = 0;
    for (int i = 0; i < 6; i++) {
        if (bitsBuffer(a[i], b[i], &c[counter], 0)) {
            counter++;
        }
    }
    if (bitsBuffer(0, 0, &c[counter], 1)) {
        counter++;
    }
    fwrite(c, sizeof(char), counter, stdout);
}

Я воспроизвел функцию на бумаге (просмотрел каждый шаг вручную) и не могу найти свою ошибку. Помощь приветствуется.

Обновлено: Ожидаемый результат (передается через xxd, должен быть идентичен массиву []):

00000000: 01101100 01011110 01100000                             V..

Фактический результат:

00000000: 01010110 10111100 00001001                             V..

Пожалуйста, отредактируйте и покажите ожидаемый и фактический результат.

Jabberwocky 16.05.2024 11:59

Наверное fwrite(c, sizeof(char), counter, stdout); -> for (int i = 0; i < counter; i++) printf("%02x ", (unsigned char)c[i]);. Объясните, что, по вашему мнению, делает fwrite.

Jabberwocky 16.05.2024 12:02

Если в буфере уже есть биты, вы добавляете новые биты перед ними (<<offset), а затем всегда берете 8 бит сзади. Результирующий порядок битов будет странным. Ставьте лайк aAaa + bbB + cCcc -> cbbBaAaa cCc, а это, вероятно, не то, что вы имели в виду. (правильный ?)

teapot418 16.05.2024 12:15
~0 << nBits вызывает неопределенное поведение. Измените на 0u.
Lundin 16.05.2024 12:44

Можете ли вы быть уверены, что вам не понадобится кодировать значения, отображающие более 8 бит?

Gerhardh 16.05.2024 13:28

@Gerhardh Да, я пытаюсь реализовать код Хаффмана. Это означает, что я присваиваю байтам меньшие последовательности битов. (Теоретически я мог бы использовать кодовое слово длиной более одного байта, но в большинстве случаев в этом нет необходимости)

TheGlibber 16.05.2024 13:43

Это зависит от набора значений, которые вы хотите закодировать. Для каждого значения, которое соответствует меньшему количеству битов, вы получаете больше значений, для кодирования которых требуется больше битов. Если вы хотите кодировать 8-битные целочисленные значения без каких-либо ограничений, вы либо будете использовать 8 бит для всех из них, либо для некоторых потребуется более 8 бит. (Например, если вы используете 7 бит для одного значения, вы получите 2 значения, для которых потребуется 9 бит). В этом случае использования unsigned short может быть недостаточно для вашего buffer.

Gerhardh 16.05.2024 13:46
// Very readable way to add nBits Bits to buffer? Добавление этого комментария НЕ сделало строку после него «очень читабельной». Тот факт, что вы почувствовали необходимость добавить этот комментарий, означает, что код НЕ читается.
Andrew Henle 17.05.2024 03:07
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
121
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

    if (finish) { 
        buffer = 0; // <<<==== This is a bug I think
        if (offset) {
            *outByte = (char)buffer;
            offset = 0;
            return 1;
        }
        offset = 0;
        return 0;
    }

Когда я запускаю ваши тестовые данные, когда ваш код достигает строки с пометкой «Я думаю, это ошибка», в ней все еще остаются биты. А именно buffer и 0b01001 равны 5. Эти последние пять бит никогда не записываются в выход.

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

Наверное, это примерно правильно. Это дает ожидаемый результат

int bitsBuffer2(char inByte, char nBits, char* outByte, char finish)
{
    int i;
    static short unsigned buffer = 0;
    static char unsigned totalBits = 0;

    // Note: nBits is assumed to b <= 8 !
    buffer <<= nBits; // Make space for the bits
    buffer |= inByte & ~(~0u << nBits); // Put the bits in the buffer
    totalBits += nBits;
    if (totalBits >= 8) {
        // Slice the top 8 bits off the buffer
        totalBits -= 8;
        *outByte = (char)(buffer >> totalBits); // Note: Shift before cast
        buffer &= ~(~0u << totalBits);
        return 1;
    }
    if (finish) {
        if (totalBits > 0) { // Test if anything is in the buffer
            // Pack the remaining bits at the top of the byte
            *outByte = (char)(buffer << (8 - totalBits));
            // Zap everything
            totalBits = 0;
            buffer = 0;
            return 1;
        }
        return 0;
    }
    return 0;
}

Это может быть не на 100% правильно, если nBits и много битов в буфере.

Во-первых, ваш интерфейс bitsBuffer() (возвращаемое значение истинно, если в *outByte записан один байт) сломан: если вы предоставляете оба ненулевых nBits для отправки и запрашиваете finish true, то вам может потребоваться вывести два байта, но интерфейс может вернуть только один. Вам придется потребовать, чтобы если finish истинно, то nBits должно быть равно нулю. Ваш текущий код будет игнорировать запрос finish, если предоставленный nBits приводит к выводу байта. Вам придется вызвать еще раз с еще одним finish запросом.

Во-вторых, кодирование Хаффмана позволяет легко создавать коды длиной более восьми бит даже при небольшом объеме входных данных. Ваш код должен иметь возможность обрабатывать более длинные коды. В действительности ваш интерфейс может обрабатывать только восьмибитные коды. Например, десять разных символов с частотами 1, 1, 1, 3, 4, 7, 11, 18, 29 и 47, всего 122 символа на входе, дадут два девятибитных кода. Когда вы обрабатываете более длинные коды, вашему интерфейсу также потребуется обрабатывать более одного созданного байта.

В-третьих, в зависимости от желаемого порядка битов из вашего примера вы помещаете биты задом наперед. Для вашего кода второй вызов поместит эти биты выше битов первого вызова. Таким образом, в вашем примере 0110, за которым следует 1, второе будет помещено над первым, что даст 10110. В то время как желаемый результат ставит первые биты выше вторых, 01101.

В-четвертых, ваш finish обнуляется buffer перед отправкой битов buffer! Вы преждевременно стираете свою информацию.

Например, это даст желаемый результат, будет работать с кодами длиной до 57 бит и правильно обрабатывать flush:

#include <stdint.h>

// Write the low n bits of code, 0 <= n <= 57, to out[]. The bits above the low
// n in code must be zeros. If flush is true, then any remaining unwritten bits
// are put in the high bits of the last byte provided in out[], and the
// internal buffer is emptied. The return value is the number of bytes written
// to out[], in 0..8. stuff_bits() is not re-entrant.
int stuff_bits(char *out, uint64_t code, int n, int flush) {
    static uint64_t buf;
    static int bits = 0;
    buf = (buf << n) | code;
    bits += n;
    char *put = out;
    while (bits >= 8) {
        bits -= 8;
        *put++ = buf >> bits;
    }
    if (flush && bits) {
        *put++ = buf << (8 - bits);
        bits = 0;
    }
    return (int)(put - out);
}

Спасибо, я решил удалить аргумент завершения (/flush) и сбрасывать его всякий раз, когда nBits (/n) равно нулю.

TheGlibber 17.05.2024 12:23

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