Запись пользовательских n бит данных в новый двоичный файл с использованием битовых полей - PullRequest
3 голосов
/ 16 июня 2019

Я прочитал несколько похожих тем о битовых полях, но я не понимаю этого достаточно, чтобы я мог использовать его.Вот моя проблема.У меня есть это struct R:

struct R{
   unsigned int opcode: 6;
   unsigned int rs: 5;
   unsigned int rt: 5;
   unsigned int rd: 5;
   unsigned int shamt: 5;
   unsigned int funct: 6;
};

Я использую битовые поля, чтобы определить, что моя структура состоит из 32 бит данных.Для тех, кто хочет знать, эта структура представляет собой инструкцию типа R MIPS.

Я хочу записать эти данные в файл с именем result, и для этого я использовал этот код:

struct R test  = {32,0,11,21,19,0}

FILE *fp = fopen("./result", "rb");
fwrite(&test,sizeof(test),1,result);

С этим кодом, если я запущусь в консоль xxd -b result, я ожидаю увидеть это:

00000000: 00100000 01011000 01110101 00000010

Вместо этого я получу

00000000: 00100000 10100000 01110011 00000001

Я думаю, проблемаэто fwrite, но я не совсем понимаю.

Это домашнее задание, поэтому я подумал об альтернативе:

  1. Создать массив char sequence[32], который очень индексимитирует 1 бит.
  2. Имеет структуру массивов:
struct R{
    char opcode[6];
    char rs[5];
    char rt[5];
    char rd[5];
    char shamt[5];
    char funct[6];
};
Создайте мою двоичную последовательность с объединением всех массивов. Преобразуйте каждые 8 ​​строк в шестнадцатеричные - например: 00100000 дает 0x20. Используйте putc для записив мой файл.

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

Ответы [ 2 ]

5 голосов
/ 16 июня 2019

Что стандарт не говорит

Как я отметил в комментарии,

Битовые поля являются раздражающей частью стандарта C. Большинство аспектов их поведения определяется реализацией. В частности, отображение различных полей в блоке определяется реализацией, поэтому, если поле opcode занимает наиболее значимые 6 битов или младшие 6 битов, определяется реализацией.

См. C11 §6.7.2.1 Спецификаторы структуры и объединения , особенно ¶10 и далее.

Стандарт C не предусматривает расположение битовых полей; это просто говорит о том, что реализация должна документировать, что она делает. Если вы обнаружите, что, когда opcode указан первым, он идет в младших битах, то пусть будет так; это то, что делает ваш компилятор. Если вы хотите, чтобы это было в старших разрядах, вам, вероятно, нужно переместить его на другой конец структуры (и вам нужно будет также изменить порядок других полей). Все зависит от компилятора - хотя компилятор, вероятно, будет соответствовать платформе ABI. См. Документацию GCC по Поведение, определяемое реализацией: например, структуры, объединения, перечисления и битовые поля . Есть места, где GCC ссылается на платформу ABI (и относится к ней). Вы можете найти информацию ABI через Google - то, что вы найдете, не обязательно очень читабельно, но информация есть.

Код для анализа вашей структуры

Вот некоторый код, основанный на вашей структуре (и некоторый код форматирования двоичных чисел):

#include <stdio.h>
#include <assert.h>

static
void format_binary8v(unsigned char x, int n, char buffer[static 9])
{
    assert(n > 0 && n <= 8);
    int start = 1 << (n - 1);
    for (int b = start; b != 0; b /= 2)
    {
        *buffer++ = ((b & x) != 0) ? '1' : '0';
        x &= ~b;
    }
    *buffer = '\0';
}

static
void format_binary32(unsigned int x, char buffer[static 33])
{
    for (unsigned b = 2147483648; b != 0; b /= 2)
    {
        *buffer++ = ((b & x) != 0) ? '1' : '0';
        x &= ~b;
    }
    *buffer = '\0';
}

struct R
{
    unsigned int opcode : 6;
    unsigned int rs : 5;
    unsigned int rt : 5;
    unsigned int rd : 5;
    unsigned int shamt : 5;
    unsigned int funct : 6;
};

static void dump_R(const char *tag, struct R r)
{
    union X
    {
        struct R r;
        unsigned int i;
    };

    printf("%s:\n", tag);
    union X x = { .r = r };
    char buffer[33];
    format_binary32(x.i, buffer);
    printf("Binary: %s\n", buffer);
    format_binary8v(x.r.opcode, 6, buffer);
    printf(" - opcode: %s\n", buffer);
    format_binary8v(x.r.rs, 5, buffer);
    printf(" - rs:      %s\n", buffer);
    format_binary8v(x.r.rt, 5, buffer);
    printf(" - rt:      %s\n", buffer);
    format_binary8v(x.r.rd, 5, buffer);
    printf(" - rd:      %s\n", buffer);
    format_binary8v(x.r.shamt, 5, buffer);
    printf(" - shamt:   %s\n", buffer);
    format_binary8v(x.r.funct, 6, buffer);
    printf(" - funct:  %s\n", buffer);
}

int main(void)
{
    char filename[] = "filename.bin";
    FILE *fp = fopen(filename, "w+b");
    if (fp == NULL)
    {
        fprintf(stderr, "failed to open file '%s' for reading and writing\n", filename);
        return 1;
    }
    //struct R test  = {32, 0, 11, 21, 19, 0};
    struct R test  = { 32, 7, 11, 21, 19, 3 };

    fwrite(&test, sizeof(test), 1, fp);

    dump_R("test - after write", test);

    rewind(fp);
    fread(&test, sizeof(test), 1, fp);
    dump_R("test - after read", test);

    fclose(fp);
    return 0;
}

При запуске на MacBook Pro с MacOS 10.14.5 Mojave с GCC 9.1.0 я получаю:

test - after write:
Binary: 00001110011101010101100111100000
 - opcode: 100000
 - rs:      00111
 - rt:      01011
 - rd:      10101
 - shamt:   10011
 - funct:  000011
test - after read:
Binary: 00001110011101010101100111100000
 - opcode: 100000
 - rs:      00111
 - rt:      01011
 - rd:      10101
 - shamt:   10011
 - funct:  000011

И необработанный двоичный выходной файл:

$ xxd -b filename.bin
00000000: 11100000 01011001 01110101 00001110                    .Yu.
$

Моя интерпретация заключается в том, что на моей машине данные для битового поля opcode находятся в младших 6 битах единицы хранения, данные для битового поля funct находятся в старших 6 битах, и другие элементы находятся между ними. Это понятно при взгляде на 32-битное значение. То, как xxd -b разбивает его, требует большего объяснения:

  • Первый байт - младший байт - архитектура Intel.
  • Содержит все 6 бит opcode в его младших разрядах; он также содержит два младших значащих бита rs в своих старших значащих битах.
  • Второй байт содержит три старших значащих бита rs в качестве его младших значащих битов и все 5 битов из rt в качестве его старших битов.
  • Третий байт содержит все 5 бит rd в его младших разрядах и 3 младших бита shamt в его старших разрядах.
  • Четвертый и самый значимый бит содержит 2 старших значащих бита shamt в его младших значащих битах и ​​все 6 бит funct в его старших значащих битах.

Это все немного ошеломляет!

Когда я возвращаюсь к вашим значениям для структуры test (struct R test = {32, 0, 11, 21, 19, 0};), я получаю:

test - after write:
Binary: 00000010011101010101100000100000
 - opcode: 100000
 - rs:      00000
 - rt:      01011
 - rd:      10101
 - shamt:   10011
 - funct:  000000
test - after read:
Binary: 00000010011101010101100000100000
 - opcode: 100000
 - rs:      00000
 - rt:      01011
 - rd:      10101
 - shamt:   10011
 - funct:  000000

и

00000000: 00100000 01011000 01110101 00000010                     Xu.

Ваше оборудование и / или компилятор отличается от моего; у него могут быть разные правила для размещения битовых полей.

Обратите внимание, что этот код предполагает без тестирования, что unsigned или unsigned int является 32-битной величиной. Если вы находитесь в системе, где это не соответствует действительности, вам необходимо пересмотреть код, чтобы использовать такие типы, как uint32_t и uint8_t и т. Д., Как указано в <stdint.h> (и спецификаторы формата, как указано в <inttypes.h>).

уточненный код

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

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct R
{
    unsigned int opcode : 6;
    unsigned int rs     : 5;
    unsigned int rt     : 5;
    unsigned int rd     : 5;
    unsigned int shamt  : 5;
    unsigned int funct  : 6;
};

static void test_r(const char *tag, struct R r, FILE *fp);
static void run_xxd(const char *file);

int main(void)
{
    char filename[] = "filename.bin";
    FILE *fp = fopen(filename, "w+b");
    if (fp == NULL)
    {
        fprintf(stderr, "failed to open file '%s' for reading and writing\n", filename);
        return 1;
    }

    struct R r[]  =
    {
        { 32,  0, 11, 21, 19,  0 },
        { 32,  7, 11, 21, 19,  3 },
        {  6, 21, 10, 14, 10,  8 },
    };
    enum { NUM_R = sizeof(r) / sizeof(r[0]) };

    for (int i = 0; i < NUM_R; i++)
    {
        char name[16];
        snprintf(name, sizeof(name), "r%d", i+1);
        test_r(name, r[i], fp);
    }

    fclose(fp);

    run_xxd(filename);

    return 0;
}

static void run_one_xxd(const char *command, const char *filename)
{
    char cmd[256];
    snprintf(cmd, sizeof(cmd), "%s %s", command, filename);
    printf("\nCommand: %s\n", cmd);
    fflush(stdout);
    system(cmd);
    putchar('\n');
}

static void run_xxd(const char *filename)
{
    run_one_xxd("xxd -c 4 -b     ", filename);
    run_one_xxd("xxd -c 4 -g 1 -u", filename);
}

static void format_binary8v(unsigned char x, int n, char buffer[static 9]);
static void format_binary32(unsigned x, char buffer[static 33]);
static void dump_bitfield(int nbits, unsigned value, const char *name);
static void dump_bytes(const char *tag, struct R r);
static void dump_R(const char *tag, struct R r);

static void test_r(const char *tag, struct R r, FILE *fp)
{
    char buffer[32];
    long offset = sizeof(struct R);
    putchar('\n');
    fwrite(&r, sizeof(r), 1, fp);
    snprintf(buffer, sizeof(buffer), "%s - after write", tag);
    dump_R(buffer, r);
    fseek(fp, -offset, SEEK_CUR);
    struct R s;
    fread(&s, sizeof(s), 1, fp);
    fseek(fp, 0, SEEK_CUR);             // Ready for reading or writing!
    snprintf(buffer, sizeof(buffer), "%s - after read", tag);
    dump_R(buffer, s);
    /* Safe regardless of whether struct R uses all bits in its storage unit */
    assert(r.opcode == s.opcode);
    assert(r.rs     == s.rs    );
    assert(r.rs     == s.rs    );
    assert(r.rs     == s.rs    );
    assert(r.shamt  == s.shamt );
    assert(r.funct  == s.funct );
    /* Only safe because struct R uses all bits of its storage unit */
    assert(memcmp(&r, &s, sizeof(struct R)) == 0);
}

static void dump_R(const char *tag, struct R r)
{
    printf("%s:\n", tag);
    dump_bytes("Binary", r);
    dump_bitfield(6, r.opcode, "opcode");
    dump_bitfield(5, r.rs,     "rs");
    dump_bitfield(5, r.rt,     "rt");
    dump_bitfield(5, r.rd,     "rd");
    dump_bitfield(5, r.shamt,  "shamt");
    dump_bitfield(6, r.funct,  "funct");
}

static void dump_bytes(const char *tag, struct R r)
{
    union X
    {
        struct R r;
        unsigned i;
    };
    union X x = { .r = r };
    char buffer[33];
    printf("%s: 0x%.8X\n", tag, x.i);
    format_binary32(x.i, buffer);
    //printf("%s: MSB %s LSB\n", tag, buffer);
    printf("%s: MSB", tag);
    for (int i = 0; i < 4; i++)
        printf(" %.8s", &buffer[8 * i]);
    puts(" LSB (big-endian)");
    printf("%s: LSB", tag);
    for (int i = 0; i < 4; i++)
        printf(" %.8s", &buffer[8 * (3 - i)]);
    puts(" MSB (little-endian)");
}

static void dump_bitfield(int nbits, unsigned value, const char *name)
{
    assert(nbits > 0 && nbits <= 32);
    char vbuffer[33];
    char nbuffer[8];
    snprintf(nbuffer, sizeof(nbuffer), "%s:", name);
    format_binary8v(value, nbits, vbuffer);
    printf(" - %-7s  %6s  (%u)\n", nbuffer, vbuffer, value);
}

static
void format_binary8v(unsigned char x, int n, char buffer[static 9])
{
    assert(n > 0 && n <= 8);
    int start = 1 << (n - 1);
    for (int b = start; b != 0; b /= 2)
    {
        *buffer++ = ((b & x) != 0) ? '1' : '0';
        x &= ~b;
    }
    *buffer = '\0';
}

static
void format_binary32(unsigned x, char buffer[static 33])
{
    for (unsigned b = 2147483648; b != 0; b /= 2)
    {
        *buffer++ = ((b & x) != 0) ? '1' : '0';
        x &= ~b;
    }
    *buffer = '\0';
}

Выводит:

r1 - after write:
Binary: 0x02755820
Binary: MSB 00000010 01110101 01011000 00100000 LSB (big-endian)
Binary: LSB 00100000 01011000 01110101 00000010 MSB (little-endian)
 - opcode:  100000  (32)
 - rs:       00000  (0)
 - rt:       01011  (11)
 - rd:       10101  (21)
 - shamt:    10011  (19)
 - funct:   000000  (0)
r1 - after read:
Binary: 0x02755820
Binary: MSB 00000010 01110101 01011000 00100000 LSB (big-endian)
Binary: LSB 00100000 01011000 01110101 00000010 MSB (little-endian)
 - opcode:  100000  (32)
 - rs:       00000  (0)
 - rt:       01011  (11)
 - rd:       10101  (21)
 - shamt:    10011  (19)
 - funct:   000000  (0)

r2 - after write:
Binary: 0x0E7559E0
Binary: MSB 00001110 01110101 01011001 11100000 LSB (big-endian)
Binary: LSB 11100000 01011001 01110101 00001110 MSB (little-endian)
 - opcode:  100000  (32)
 - rs:       00111  (7)
 - rt:       01011  (11)
 - rd:       10101  (21)
 - shamt:    10011  (19)
 - funct:   000011  (3)
r2 - after read:
Binary: 0x0E7559E0
Binary: MSB 00001110 01110101 01011001 11100000 LSB (big-endian)
Binary: LSB 11100000 01011001 01110101 00001110 MSB (little-endian)
 - opcode:  100000  (32)
 - rs:       00111  (7)
 - rt:       01011  (11)
 - rd:       10101  (21)
 - shamt:    10011  (19)
 - funct:   000011  (3)

r3 - after write:
Binary: 0x214E5546
Binary: MSB 00100001 01001110 01010101 01000110 LSB (big-endian)
Binary: LSB 01000110 01010101 01001110 00100001 MSB (little-endian)
 - opcode:  000110  (6)
 - rs:       10101  (21)
 - rt:       01010  (10)
 - rd:       01110  (14)
 - shamt:    01010  (10)
 - funct:   001000  (8)
r3 - after read:
Binary: 0x214E5546
Binary: MSB 00100001 01001110 01010101 01000110 LSB (big-endian)
Binary: LSB 01000110 01010101 01001110 00100001 MSB (little-endian)
 - opcode:  000110  (6)
 - rs:       10101  (21)
 - rt:       01010  (10)
 - rd:       01110  (14)
 - shamt:    01010  (10)
 - funct:   001000  (8)

Command: xxd -c 4 -b      filename.bin
00000000: 00100000 01011000 01110101 00000010   Xu.
00000004: 11100000 01011001 01110101 00001110  .Yu.
00000008: 01000110 01010101 01001110 00100001  FUN!

Command: xxd -c 4 -g 1 -u filename.bin
00000000: 20 58 75 02   Xu.
00000004: E0 59 75 0E  .Yu.
00000008: 46 55 4E 21  FUN!
3 голосов
/ 16 июня 2019

Я протестировал ваш пример и получаю ожидаемые результаты:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int binpr(unsigned char Byte, FILE *f)
{
    char buf[8];
    for(int i=0; i<8; i++) buf[i]=(char)(((Byte>>(7-i))&1)+'0');
    return (int)+fwrite(buf,1,8,f);
}
struct R{

   unsigned int opcode: 6;
   unsigned int rs: 5;
   unsigned int rt: 5;
   unsigned int rd: 5;
   unsigned int shamt: 5;
   unsigned int funct: 6;
};
int main()
{

    struct R test  = {32,0,11,21,19,0};
    system(": > result"); //rb requires that the file already exists
    FILE *fp = fopen("./result", "rb+");
    if(!fp) return perror("fopen"),1;
    if(1!=fwrite(&test,sizeof(test),1,fp)) return perror("fwrite"),1;
    rewind(fp);
    char buf[sizeof(struct R)];
    if(1!=fread(&buf,sizeof(buf),1,fp)) return perror("fread"),1;
    fputs(" ",stdout); if(0!=memcmp(buf,&test,sizeof test)) abort();
    for(size_t i=0; i<sizeof(test); i++) { binpr(*((unsigned char*)&test+i),stdout); fputs(" ",stdout); } puts("");
    system("xxd -b result |cut -d: -f2");

     /*OUTPUT:*/
     /*00100000 01011000 01110101 00000010 */
     /*00100000 01011000 01110101 00000010                     Xu.*/
}

Обратите внимание, что для открытия файла для обновлений и чтения вам нужно "rb+" вместо просто "rb".В противном случае вы получите ошибки на fwrite (которые вы не увидите, потому что вы не выполняете никакой проверки ошибок).

(ваш компилятор также устанавливает битовые поля необычным способом, хотя это также возможно, хотяэто, вероятно, менее вероятно, чем ошибочно указанный fopen флаг.)

...