0

I have a struct in c like this

struct RegisterStruct
{
    uint64_t b_0 : 64;
    uint64_t b_1 : 64;
    uint64_t c_0 : 64;
    uint64_t c_1 : 64;
    uint64_t c_2 : 64;
    uint64_t d_0 : 64;
    uint64_t d_1 : 64;
};

I would like to concatenate the fields into an uint64_t integer. Each of the fields should occupy a given number of bits defined as follows:

b_0: 4bits 
b_1: 4bits
c_0: 8bits
c_1: 8bits
c_2: 8bits
d_1: 16bits
d_2: 16bits

The result should be an uint64_t integer containing the concatenated bit fields(from b_0 to d_2) each occupying the given number of bits.

Here is what I have tried but I don't think this solution is correct:

struct RegisterStruct Register;

   Register.b_0 = 8;
   Register.b_1 = 8;
   Register.c_0 = 128;
   Register.c_1 = 128;
   Register.c_2 = 128;
   Register.d_0 = 32768;
   Register.d_1 = 32768;

   uint64_t reg_frame =Register.b_0<<60|Register.b_1<<56|Register.c_0<<48|Register.c_1<<40|Register.c_2<<32|Register.d_0<<16|Register.d_1;
Kinyugo
  • 429
  • 1
  • 4
  • 11
  • *"... but I don't think this solution is correct"* Why is it not correct? What is the problem you are having? – user694733 Mar 18 '20 at 10:10
  • 2
    In your posted `struct RegisterStruct`, all the bit-field widths are `64`. Is that a cut and paste error? Surely you want them to be 4, 4, 8, 8, 8, 16 and 16? – Ian Abbott Mar 18 '20 at 10:18
  • Your solution is correct. I naturally understand bit order from the least significant bit, so I would put `b_0: 4bits` as the least significant bits. – KamilCuk Mar 18 '20 at 10:33
  • @IanAbbott the bit-field width are 64 to allow for the shift operation. – Kinyugo Mar 18 '20 at 11:43
  • 2
    If you shift bit-fields, cast them to an unsigned integer type of sufficient width, even if the base type they are declared with is such a type. As [we have seen recently](https://stackoverflow.com/questions/60718832/inconsistent-truncation-of-unsigned-bitfield-integer-expressions-between-c-and), the C standard is unclear about what the actual type of a bit-field is, so a `uint64_t x : 4` may act like a (for example) 32-bit `int` in an expression, and then `Register.x << 60` would have a shift amount outside the `int` bounds and have behavior not defined by the C standard. – Eric Postpischil Mar 18 '20 at 11:53
  • The solution I have gives me a negative number. I have come to understand that the negative number is from the two's complement. I would how ever like to get the `uint64_t` integer from the original bits. @KamilCuk – Kinyugo Mar 18 '20 at 14:24
  • 1
    @Kinyugo I don't see the point in making all the bit-fields 64 bits wide. They will (probably) behave the same as non-bit-field `uint64_t` members. – Ian Abbott Mar 18 '20 at 14:35
  • @IanAbbott its true I have rectified that. The `uint64_t reg_frame` however gets assigned a decimal converted from the 2's complement of the binary representation. Can you help me with this problem as I need the actual decimal not the two's complement decimal. – Kinyugo Mar 18 '20 at 15:15
  • @Kinyugo I don't understand what you mean. With the code you posted, `reg_frame` will get the value `0x8880808080008000` (decimal 9836002875568848896). Were you expecting a different value? – Ian Abbott Mar 18 '20 at 15:52
  • @Kinyugo `The solution I have gives me a negative number` - that's not possible `uint64_t` is _unsigned_ so it can't be negative. Could be you are _printing_ the number as negative number. – KamilCuk Mar 18 '20 at 15:56

2 Answers2

1

You can put the structure containing the bit fields in a union with the full 64-bit unsigned integer like this:

union RegisterUnion
    struct
    {
        uint64_t b_0 : 4;
        uint64_t b_1 : 4;
        uint64_t c_0 : 8;
        uint64_t c_1 : 8;
        uint64_t c_2 : 8;
        uint64_t d_0 : 16;
        uint64_t d_1 : 16;
    };
    uint64_t val;
};

The main problem with the above is that it is not portable. The C standard leaves the order in which bit fields are packed into their underlying storage unit type (uint64_t in this case) as an implementation defined decision. This is entirely separate from the ordering of bytes within a multi-byte integer, i.e. the little endian versus big endian byte ordering.

In addition, using uint64_t as the base type of a bit-field might not be supported. An implementation is only required to support bit-field members of types _Bool, signed int and unsigned int (or qualified versions thereof). According to the C11 draft 6.7.2.1 paragraph 5:

  1. A bit-field shall have a type that is a qualified or unqualified version of _Bool, signed int, unsigned int, or some other implementation-defined type. It is implementation-defined whether atomic types are permitted.
Ian Abbott
  • 15,083
  • 19
  • 33
0
typedef union
{ 
    struct
    {
        uint64_t b_0 : 4;
        uint64_t b_1 : 4;
        uint64_t c_0 : 8;
        uint64_t c_1 : 8;
        uint64_t c_2 : 8;
        uint64_t d_0 : 16;
        uint64_t d_1 : 16;
    };
    uint64_t u64;
}R_t;


int main()
{

    R_t Register;

    Register.b_0 = 8;
    Register.b_1 = 8;
    Register.c_0 = 128;
    Register.c_1 = 128;
    Register.c_2 = 128;
    Register.d_0 = 32768;
    Register.d_1 = 32768;

    printf("%llx\n", (long long unsigned)Register.u64);
}

https://godbolt.org/z/_dYuz2

0___________
  • 60,014
  • 4
  • 34
  • 74