Welcome, guest! We are a creative arts/general discussion community, and welcome guests and members alike to participate in our discussions. While registration is not required to post, we strongly recommend considering an account with us, as it a quick, free, and easy way to fully utilize all of the features on our forum. We hope you enjoy your stay!


Thread Rating:
  • 0 Vote(s) - 0 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
Thanks given by: Guardian




Users browsing this thread: 1 Guest(s)

Contact Us | Makestation | Return to Top | Lite (Archive) Mode | RSS Syndication
Proudly powered by MyBB 1.8, © 2002-2019 MyBB Group. Hosted by Ramnode.
Design/theme © 2014 by Makestaton designers.
All rights reserved.
Also see Chatcave chat hosting (owned by us), Forumonic.com (a Harry-K community) and Zalost's Gridzone