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.