Step 4 of 5
Flags
A common use of bits is flags: packing several yes/no settings into a single integer, one bit each. File permissions, network protocol headers, graphics options and hardware registers all work this way.
Give each flag its own bit with a shift:
#define BOLD (1u << 0) /* 001 */
#define ITALIC (1u << 1) /* 010 */
#define UNDERLINE (1u << 2) /* 100 */
Then four operations cover everything:
| Goal | Code |
|---|---|
| turn a flag on | style |= ITALIC; |
| turn a flag off | style &= ~ITALIC; |
| toggle a flag | style ^= ITALIC; |
| test a flag | if (style & ITALIC) |
#include <stdio.h>
#define BOLD (1u << 0)
#define ITALIC (1u << 1)
#define UNDERLINE (1u << 2)
int main(void) {
unsigned style = BOLD | UNDERLINE;
printf("%u\n", style);
style ^= BOLD;
style |= ITALIC;
printf("%u bold=%d italic=%d\n", style, (style & BOLD) != 0, (style & ITALIC) != 0);
return 0;
}
5
6 bold=0 italic=1
Why &= ~FLAG turns a flag off
~ITALIC has every bit set except the italic bit (...11101). AND-ing with it keeps every other bit as it was and forces the italic bit to 0.
Why != 0 when testing
style & ITALIC is either 0 or the flag's value (here 2), not 1. As a condition in if that's fine, since non-zero is true. When you want an actual 0 or 1, compare with != 0 (or use !!).
Combining flags with | when calling a function (like BOLD | ITALIC) is a pattern you'll see in many C APIs, including open() on Unix.
Bit-fields
C can also pack small fields into an integer for you, with bit-fields:
struct Style {
unsigned bold : 1; /* 1 bit: 0 or 1 */
unsigned italic : 1;
unsigned size : 6; /* 6 bits: 0 to 63 */
};
Each member uses only the number of bits after the colon, and you read and write it like any member (s.size = 12;). That saves memory in big arrays of small records. But the compiler chooses the exact layout (which end the bits start from, padding), so bit-fields are not a portable way to match a file format or a hardware register. Use masks and shifts, as above, for that.
Byte order
A 4-byte int like 0x12345678 is stored as 4 separate bytes, and machines disagree about their order. Little-endian machines, which include x86 and ARM as it's almost always configured (so nearly every PC and phone, and WebAssembly), store the lowest byte first: 78 56 34 12. Big-endian machines store 12 34 56 78, and network protocols traditionally send numbers in big-endian order. Shifts and masks work on values, so (x >> 8) & 0xFF gives the same answer everywhere. Byte order only matters when you look at memory one byte at a time, or read and write raw bytes, as binary files do in the next module.
Your turn: fill in the operators.