C Language Reference for Script Programmers - Bit Fields

Bit Fields

Overview                                                                                                                                                                      

Very often in programming we have the need for variables that act like switches, they are either on or off. This information could be stored in an int, and we could give it a value of 1 to indicate that the switch is on, or we could give it a value of 0 to indicate that it is off.

This might not be the most efficient use of memory though, especially if we have a group of data that is all related. An example of a group of data that are all related and simply have an on/off state are the switches in the MotionBend block of a Flex Motion effect:

  • Affect Before IK
  • Filter Hierarchy Motion
  • Filter Point Rotation
  • Twist Chain
  • Bank Children
  • Scale Children XForm

We could store the states of all of these pieces of information in one variable. Sure we could create a structure with 6 ints, but we could store all of that information into a single int and still have plenty of memory to spare.

Remember that an int is made up of 4 bytes, and each of those bytes is made up of 8 bits. Therefore an integer is made up of 32 bits. That’s 32 values that can be used to store the information above. What we need is some way to access information on the bit level of a variable. While we don’t have any direct method of accessing this information we can infer it through the use of boolean algebra. 

Boolean Algebra                                                                                                                                                         

The basics of boolean algebra are not at all difficult. It is based on the following operations:

  • AND
  • OR
  • NOT
  • XOR    (exclusive or)

There are more but this will get us started.

Boolean algebra deals only with two values, 0 and 1. So the above operations have the following properties:

1 AND 1 = 1
1 AND 0 = 0
0 AND 0 = 0 

1 OR 1 = 1
1 OR 0 = 1
0 OR 1 = 1
0 OR 0 = 0

NOT 1 = 0
NOT 0 = 1

1 XOR 1 = 0
1 XOR 0 = 1
0 XOR 1 = 1
0 XOR 0 = 0

To put it into words:

AND: if both values are 1 then the result will be 1, otherwise it will be 0.

OR: if either or both of the values are 1 then the result will be 1, otherwise it will be 0.

NOT: not is a unary operator (it only takes one operand), the result is the opposite of the operand. If the operand is 1 the result is 0. If the operand is 0 the result is 1.

XOR: same as OR, except that the result will be 0 if both of the operands are 1.

Now we can’t have a variable that is 1 bit in size, at least not the I’m aware of. So what we end up with is dealing with groups of bits all at once, 32 or 64 bits for that matter. For example a field might look something like this:

01000111100010101111000000110001

but we only be interested in the 5th bit. Incidentally numbering starts at the right, so the 5th digit is the 5th from the right. So how can we infer the value of that digit using the above boolean algebra operators? Well if we had another bit field that had only a single 1 in it, at the 5th digit, we could use AND:

01000111100010101111000000110001
AND
00000000000000000000000000010000
=
00000000000000000000000000010000

That might be useful. What would the result have been had the 5th digit been a 0? 

01000111100010101111000000100001
AND
00000000000000000000000000010000
=
00000000000000000000000000000000

Now we’re getting somewhere. If the bit is set in both fields then we get a non-zero number. If the result is 0 then we know that the bit is not set in the first (because we have explicitly set it in the second). It turns out that the if control structure considers 0 to be false, and any non-zero number to be true. So we could have something like:

if
01000111100010101111000000110001
AND
00000000000000000000000000010000
do something, otherwise don’t

The second bit field is referred to as a bit mask. We call it a mask because it only has the bits set that we are interested in. So we use it to “mask out” the information we’re not interested in. But how did we set it’s value to begin with?

Keep in mind that these bit fields represent the bits that make up an integer. So all of these 1’s and 0’s actually represent some integer value. It turns out that the binary number 00000000000000000000000000010000 or simply 10000, is equivalent to the decimal number 16. I won’t go into number system theory here, you can do that on your own, but here’s a list of each of the binary digits and their hexadecimal equivalents:

Digit

Binary

Hexadecimal

1

1

0x1

2

10

0x2

3

100

0x4

4

1000

0x8

5

10000

0x10

6

100000

0x20

7

1000000

0x40

8

10000000

0x80

9

100000000

0x100

10

1000000000

0x200

11

10000000000

0x400

12

100000000000

0x800

13

1000000000000

0x1000

14

10000000000000

0x2000

15

100000000000000

0x4000

16

1000000000000000

0x8000

17

10000000000000000

0x10000

18

100000000000000000

0x20000

19

1000000000000000000

0x40000

20

10000000000000000000

0x80000

21

100000000000000000000

0x100000

22

1000000000000000000000

0x200000

23

10000000000000000000000

0x400000

24

100000000000000000000000

0x800000

25

1000000000000000000000000

0x1000000

26

10000000000000000000000000

0x2000000

27

100000000000000000000000000

0x4000000

28

1000000000000000000000000000

0x8000000

29

10000000000000000000000000000

0x10000000

30

100000000000000000000000000000

0x20000000

31

1000000000000000000000000000000

0x40000000

32

10000000000000000000000000000000

0x80000000

 It is common practice to use hex values instead of decimal values when assigning a bit value as there is a more definite pattern to the numbers when dealing with hex. Of course if you were to find the decimal equivalents of each of these numbers you’d certainly notice a pattern, but one that can be difficult to remember when you get to the high numbers:

2
4
8
16
32
64
128
256
 

as you can see the nth digit is 2n. Ok quick what’s 223? It’s 4,194,304, and if you knew the answer to that… your sick. But after a little practice, figuring out the hex equivalent of a bit set in the 23rd binary position isn’t all that difficult (note that I’ve separated the table above into 4 bytes, and each contains 2 full decimal places; hint, hint).

Example                                                                                                                                                                      

  So from our example earlier where we needed to set the 5th bit to use as a mask we would have:

int  bit_mask = 0x10;

Now for the whole thing in code:

#define         FIRST_POS       0x1
#define         SECOND_POS      0x2
#define         THIRD_POS       0x4
#define         FOURTH_POS      0x8
#define         FIFTH_POS       0x10 

int  myBitField = 0x478af031;

if( myBitField & FIFTH_POS )
{
    

}

Ok,  I got some ‘splaining to do. The first group of lines, the ones with #define in them, are macro definitions. For the first one I’m defining a macro called FIRST_POS that expands to 0x1. This expansion happens just before the code is compiled, the preprocessor will go through your code and replace all occurrences of FIRST_POS with 0x1. This is exactly the same thing as doing a search and replace in a text editor. We do this though to make our code more readable. The names FIRST_POS, SECOND_POS etc.  are fairly generic names, you could certainly give them more meaningful names in the context that you’re using them in. Again taking an example from the code written out by develop:

mod->access_flags[ACCESS_GENERAL] =
          GEN_GET_GLOBALS|GEN_GET_INTERFACE_GLOBALS;

Here GEN_GET_GLOBALS and GEN_GET_INTERFACE are macros that represent bit masks (incidentally ACCESS_GENERAL is a macro that represents an array index).

The other thing that needs explaining in the code snippet is the symbol &. The ampersand is the AND operator in C. So in the if statement we are ANDing the two fields together and if the result is non-zero we execute the code that follows. You’ll notice in our example from develop, the use of the | symbol. This symbol, called a pipe, is the OR operator in C.

You can use the | to set a particular bit in an existing bit field like so:

someVar = someVar | FIRST_POS;

now someVar will have the first bit set. You could make it a little simpler with the |= operator: 

someVar |= FIRST_POS;

Both mean the same thing (see one of the reference books for more info).

So in the code from develop the value of mod->access_flags[ACCESS_GENERAL] will be a bit field with only the bits set that correspond to the masks GEN_GET_GLOBALS and GEN_GET_INTERFACE_GLOBALS.

These are the bit wise operators in C:

Operator Name

Symbol

AND

& (ampersand)

OR

| (pipe)

XOR

^ (caret)

NOT

~ (tilde)

There is a great deal more to working with bits in C, we haven’t even touched shifting, so I’d really recommend reading more on the subject. Also if you haven’t found out already, the calculator that ships with Windows has a scientific mode (View->Scientific) that allows you to work in decimal, hex, oct and binary. This is probably the most useful tool when learning other number systems.

Converted from CHM to HTML with chm2web Pro 2.82 (unicode)