Fundamental C - Basic Bits |
Written by Harry Fairhead | |||||||||||
Tuesday, 23 March 2021 | |||||||||||
Page 1 of 2 This extract, from my book on programming C in an IoT context explains the C basics of bit manipulation. It is core to working with C so much so that you can almost claim that if you aren't doing bit manipulation you probably are using the wrong language... Fundamental C: Getting Closer To The MachineNow available as a paperback and ebook from Amazon.
Also see the companion volume: Applying C <ASIN:1871962609> <ASIN:1871962463> <ASIN:1871962617> <ASIN:1871962455>
Bit manipulation is almost dead in high-level languages and it isn’t as common in C as it once was. If you are writing low-level programs that interact in any way with the hardware, however, then bit manipulation will still be an essential part of what you do and, to get things right and to make sure you are doing things in sensible ways, you need to master the technique. The good news is that it isn’t difficult once you start to think about the contents of memory as a bit pattern that has many interpretations. The Bitwise OperatorsC has a number of operators designed to allow you to perform bit manipulation. There are four bitwise operators:
As you would expect the NOT operator has the highest priority. Notice that there are also corresponding Boolean operators &&, || and ! which only work with Boolean values – with zero as false and anything non-zero as true - and not with bit patterns. The bitwise operators work with integer types. For example: int a = 0xF0; int b = 0xFF; int c = ~a & b; printf("%X\n",c) This first works out the bitwise NOT of a, i.e. 0F. This is then bitwise ANDed with b, i.e. 0F & FF which is F. The %X format specifier prints the value in hex. You can use %x for lower case and %d for decimal. Signed v UnsignedNow we come to a subtle and troublesome point. Bitwise operators are only uniquely defined for unsigned values. The reason is that unsigned values have an unambiguous representation in binary and hence the operations are well-defined in terms of the values the bit patterns represent. That is: unsigned int value=5; is always 0101 and hence: value | 0x2 is not only always 0111, but also always represents +7. The same is not true for signed values simply because the way in which negative numbers are represented isn’t fixed. The most common representation is two’s complement, but this is not part of the standard and so logical operations on unsigned numbers are implementation-dependent. Notice that this does not mean logical operations on signed values are undefined behavior – if this was the case most C programs would stop working properly. In addition “implementation-dependent” has one fairly consistent meaning: take the bit pattern that represents the value and perform the specified logical operation as if everything involved was an unsigned value. This is the only sane way to deal with the problem and it is exactly what you would expect. So, where the bit pattern isn’t defined, as in: signed int value=-5; if the machine uses two's complement representation, see Chapter 5, then the bit pattern is: 11111111111111111111111111111011 and value | 0x4 changes the third bit to 1 i.e. -1 in two’s complement. As long as you assume a representation for the signed value then the logical operation is usually well defined as the bitwise application of the operator on the bit patterns. It is the representation that is implementation-dependent and not the operation. MasksSo what do you use the bitwise logical operators for? In many cases you have the problem of setting or clearing particular bits in a value. The value is usually stored in a variable that is usually regarded as a status variable or flag. You can set and unset bits in a flag using another value, usually called a mask, that defines the bits to be changed. For example if you only want to change the first (least significant) bit then the mask would be 0x01. If you wanted to change the first and second bits the mask would be 0x03 and so on. If you find working out the correct hexadecimal value needed for any particular mask difficult then you could use the strtol function with a radix of two. For example: char *prt; int a = strtol("01",&prt,2); sets a to 0x01 and: int a = strtol("11",&prt,2); sets a to 0x03 and so on. To create a mask just write down a string of zeros and ones with ones in the positions you want to change and use strtol to convert to a mask value. Now that you have a mask what do you do with it? Suppose the variable mask contains a value that in binary has a one at each bit location you want to change. Then: flag | mask; returns a bit pattern with the same bits set to one as in the mask. Notice that the bits that the mask doesn't specify, i.e. are zero in the mask, are left at their original values. For example: char *prt; int mask = strtol("11",&prt,2); int flag = 0xFFF0; int result = flag | mask; printf("%X\n",result); sets result to 0xFFF3, i.e. it sets the first (least significant) two bits. If you use: flag & ~mask; then the bits specified in the mask are set to zero - or are unset if you prefer. Notice that you have to apply a NOT operator to the mask. For example: char *prt; int mask = strtol("11",&prt,2); int flag = 0xFFFF; int result = flag & ~mask; printf("%X\n",result); sets result to 0xFFFC, i.e. it unsets the first two bits. As well as setting and unsetting particular bits, you might also want to "flip" the specified bits, i.e. negate them so that if the bit was a one it is changed to a zero and vice versa. You can do this using the exclusive or (XOR) operator: flag ^ mask flips the bits specified by the mask. For example: char *prt; int mask = strtol("11",&prt,2); int flag = 0xFFFF; int result = flag ^ mask; printf("%X\n",result); sets result to 0xFFFC because it changes the lower two bits from ones to zeros. Again bits not specified by mask are unaffected. |
|||||||||||
Last Updated ( Tuesday, 23 March 2021 ) |