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:
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:
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 OR 1 = 1
NOT 1 = 0
1 XOR 1 = 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 5^{th} bit. Incidentally numbering starts at the right, so the 5^{th} digit is the 5^{th} 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 5^{th} digit, we could use AND: 01000111100010101111000000110001 That might be useful. What would the result have been had the 5^{th} digit been a 0? 01000111100010101111000000100001 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 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:
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 as you can see the n^{th} digit is 2^{n}. Ok quick what’s 2^{23}? 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 23^{rd} 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 5^{th} bit to use as a mask we would have: int bit_mask = 0x10; Now for the whole thing in code: #define
FIRST_POS 0x1 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] = 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:
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) |