Reputation: 123
I'm a computer science student.
Now, I'm working on a computer architecture project in C, which simulates a processor.
There are many types of instructions such as
31 27 26 22 21 17 16 0
---------------------------------------------------------------
| op | ra | rb | imm17 |
---------------------------------------------------------------
31 27 26 22 21 17 16 7 6 5 4 0
---------------------------------------------------------------
| op | ra | rb | imm10 | m | shamt |
---------------------------------------------------------------
31 27 26 22 21 0
---------------------------------------------------------------
| op | ra | imm22 |
---------------------------------------------------------------
So, I wanted to make a C structure which contains bit-fields corresponding to each elements such as op, ra and so on.
At first, I thought that I could use unions and nested structs.
For example, I wrote code like:
struct instr_t {
union {
uint32_t imm22 : 22;
struct {
union {
uint32_t imm17: 17;
struct {
uint8_t shamt: 5;
uint8_t mode : 2;
uint16_t imm10 : 10;
};
};
uint8_t rb : 5;
};
};
uint8_t ra : 5;
uint8_t op : 5;
}
I expected that the result of sizeof(struct instr_t) would be 4 but the reality was 12.
Maybe the nested structs got some paddings.
So, here is my qeustion:
or
Thank you!
Upvotes: 5
Views: 1278
Reputation: 136208
The bit-fields members must be stored in the same storage unit to be layed out contiguously:
struct instr_1_t {
uint32_t imm22 : 17;
uint32_t rb : 5;
uint32_t ra : 5;
uint32_t op : 5;
};
struct instr_2_t {
uint32_t shamt: 5;
uint32_t m: 2;
uint32_t imm10 : 10;
uint32_t rb : 5;
uint32_t ra : 5;
uint32_t op : 5;
};
struct instr_3_t {
uint32_t imm22 : 22;
uint32_t ra : 5;
uint32_t op : 5;
};
union instr_t {
struct {
uint32_t pad : 22;
uint32_t op : 5;
};
instr_1_t instr_1;
instr_2_t instr_2;
instr_3_t instr_3;
};
static_assert(sizeof(instr_t) == sizeof(uint32_t), "sizeof(instr_t) != sizeof(uint32_t)");
void handle_instr(instr_t i) {
switch(i.op) {
//
}
}
Upvotes: 6
Reputation: 5570
Maxim gave the correct answer.
I also suggest looking over this code to understand why sizeof instr_t was giving 12 :)
typedef struct s1{
uint8_t shamt: 5;
uint8_t mode : 2;
uint16_t imm10 : 10;
} s_1;
typedef union u1{
uint32_t imm17: 17;
s_1 member0;
} u_1;
typedef struct s2{
u_1 member1;
uint8_t rb : 5;
} s_2;
typedef union u2{
uint32_t imm22 : 22;
s_2 member3;
} u_2;
typedef struct instr_t {
u_2 member4;
uint8_t ra : 5;
uint8_t op : 5;
} s_instr;
int main(int argc, char* argv[])
{
printf("sizes s_1=%d, u_1=%d, s_2=%d, u_2=%d, s_instr=%d\n", sizeof(s_1), sizeof(u_1), sizeof(s_2), sizeof(u_2), sizeof(s_instr));
printf("uint8_t=%d, uint16_t=%d, uint32_t=%d\n", sizeof(uint8_t), sizeof(uint16_t), sizeof(uint32_t));
printf("Sizeof instr_t is %d\n", sizeof(s_instr));
}
Hope this helps!
Cheers!
Upvotes: 5
Reputation: 52530
Bitfields are not portable. You never know if the same bitfield definition gets you the same results on two different compilers. Bitfields also have let's say interesting semantics in multi-threaded programs.
Go with C++ and write a class with appropriated inlined accessors. I mean you are a computer science student, you know C++, right?
If for some mad reason your superiors demand that the code is written in C, write a struct with one uint32_t member and individual accessor functions using shift and masking operations. Obviously also inlined.
Upvotes: -2