Что стандарт не говорит
Как я отметил в комментарии,
Битовые поля являются раздражающей частью стандарта 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!