Thread Rating:
  • 1 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5

Bit Fields for Dummies

#1
I recently completed an online course on embedded applications using C.
To tell you the truth, it wasn't worth the few hours I invested in it. I more or less scanned through all the material, and most of it was intro stuff I already knew about optimization, like bit masking using logical/bitwise operators, compiler optimizations, const vs. #define, etc. Nothing particularly difficult to understand as long as you have a good foundation in computer science, ie. understanding how a CPU actually works.

But there was one thing that I did learn, and have wondered about before.

Take some data structure for example. Using C pseudocode, we can assume something that looks like this:
Code:
struct {
    //some code
}myBools;

And inside that data structure, you've got a bunch of boolean values (ie. true/false)

Code:
struct {
    bool var1;
    bool var2;

    bool var3;
    bool var4;
    bool var5;
    bool var6;
    bool var7;
    bool var8;
}myBools;

Well, note how in this specific use-case, I have eight values in this one structure. Now, Boolean values are either 0 or 1 traditionally, so how large is this data structure?
Well, 0/1 imply bits, so you might think 8 bits, or one byte.
Wrong.
In C, by default, a boolean value takes up one byte minimum, and since most other primitive data types (ie. int, double/float, char, etc.) can also be larger than one byte, we can say that the size can be much greater.

So the data structure given above isn't one byte, but it's eight bytes.

Now, this distresses me. If they're bools, they should only technically be 0 or 1 (or 0 and not 0) so we should be able to cut program size costs from an extra eight bytes to a single byte if we were to modify/read individual bit values.

And sure enough, you can mask/read/write bit values pretty easily, if you used a macro function like this:
Code:
#define MASK(x) (1<<x)

int main(){

    const char toRead = 0b01001100; //Some 1 byte value defined in binary
    uint_8 readVal; //Whatever the value of what we read will be, one byte in size

    //read a single byte from a character value
    readVal = (toRead >> 3) & MASK(3);
    //returns 1

    //toggle a single bit:
    toRead ^= MASK(3);
    //returns 0b01000100

    //to set a bit (back)
    toRead |= MASK(3);
    //returns back to 0b01001100
    return 0;
}

But as you can see, using macro functions everywhere can start to make things really unclear, especially when you start to combine them.

Instead, in this course, they covered a cool concept called a 'bit field' which does exactly this, but without the messy code.

Let's go back to our first struct of bools. Instead, we'll rewrite it:

Code:
struct {
    uint_8 var1 : 1;
    uint_8 var2 : 1;
    uint_8 var3 : 1;
    uint_8 var4 : 1;
    uint_8 var5 : 1;
    uint_8 var6 : 1;
    uint_8 var7 : 1;
    uint_8 var8 : 1;
}myBools;

So the format is slightly familiar yet still different. 
Code:
type Name : bitSize;

Realistically, any type can be used here, but I'm using single-byte ints for the sake of looking cool. What's important is the colon and size of the value in bits. Yes, you can have more than one, say three bits, and so when that field is read, you may have a value from 0-7 (or a three-bit value, 2^3)

Now, we can take another step further. What if you want to set/read a single bit rather than a specified group that you already made in the struct?
Well, you mush them all together. To do that, we use a union (or a structure that is split up and shares memory.)

Code:
union{
    uint_8 full;
    struct{
        uint_8 bit0 : 1;
        uint_8 bit1 : 1;
        uint_8 bit2 : 1;
        uint_8 bit3 : 1;
        uint_8 bit4 : 1;
        uint_8 bit5 : 1;
        uint_8 bit6 : 1;
        uint_8 bit7 : 1;
    }bits;

    struct{
        uint_8 bit0 : 3;
        uint_8 bit1 : 2;
        uint_8 bit2 : 1;
        uint_8 bit3 : 1;
        uint_8 bit4 : 1;
    }fields;
}bitField;

//Examples
int main(){
    bitField a = 0b01001100; //Same number as above.
    uint_8 readVal;

    //To read a single byte
    readVal = a.bits.bit3;
    //returns 1

    //To read the first three bits in the field:
    readVal = a.fields.bit0;
    //Returns 4

    //To set a bit:
    a.bits.bit3 = 0;
    
    //To toggle a bit:
    a.bits.bit3 ^= 1;

    //To read the value of the whole byte:
    readVal = a.full;
    //returns 0b01001100, or 76, or 0x4C

    return 0;
}

So in short, that's how you turn a structure of eight bytes into a structure of eight bits instead, without making the code completely unreadable.

It feels a little more object oriented this way, but overall, it's pretty well optimized.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  C++ Compiler for Win7 64-bit Electric2Shock 3 6,649 July 30th, 2014 at 7:11 AM
Last Post: Darth-Apple



Users browsing this thread: 2 Guest(s)

Dark/Light Theme Selector

Contact Us | Makestation | Return to Top | Lite (Archive) Mode | RSS Syndication 
Proudly powered by MyBB 1.8, © 2002-2024
Forum design by Makestation Team © 2013-2024