C

C Fundamentals

18 lessons

Progress0%
1. Introduction to C
1What is C?
2. Variables and Data Types
1Data Types in C
3. Control Flow
ConditionalsLoops
4. Functions
Defining FunctionsRecursion
5. Arrays and Pointers
Arrays and StringsPointers
6. Memory Management
Dynamic MemoryStructs and Files
7. Preprocessor & Macros
Preprocessor DirectivesMacros & Inline Functions
8. Bitwise Operations
Bitwise OperatorsBit Flags & Masking
9. Enums, Unions & typedef
Enums & typedefUnions & Complex Types
10. Multi-file Programs
Header Files & Compilation UnitsLinkage, Storage Classes & Make
All Tutorials
CBitwise Operations
Lesson 14 of 18 min
Chapter 8 · Lesson 2

Bit Flags & Masking

Bit Flags & Masking

Bit flags pack multiple boolean options into a single integer, allowing efficient storage and fast multi-option testing. This pattern is used extensively in operating system APIs, network protocols, and configuration systems.

Defining Flags

Each flag occupies one bit position. The idiomatic way is to left-shift 1 by the bit position:

c
#define FLAG_READ    (1 << 0)   /* 0b00000001 = 0x01 */
#define FLAG_WRITE   (1 << 1)   /* 0b00000010 = 0x02 */
#define FLAG_EXECUTE (1 << 2)   /* 0b00000100 = 0x04 */

This is clearer than writing hex literals directly, and it is obvious which bit position each flag occupies.

The Four Flag Operations

Given a variable flags and a flag F:

OperationCodeExplanation
Set a flag`flags= F`
Clear a flagflags &= ~FAND with complement forces the bit to 0
Toggle a flagflags ^= FXOR flips the bit
Check a flagflags & FNon-zero if bit is set

The complement ~F creates a mask with all bits set except the flag's bit, so ANDing clears only that bit.

Combining and Testing Multiple Flags

c
/* Set multiple flags at once */
flags |= (FLAG_READ | FLAG_WRITE);

/* Check if ALL of a set are present */
if ((flags & required) == required) { /* all required flags set */ }

/* Check if ANY of a set are present */
if (flags & any_of) { /* at least one flag set */ }

Practical Example: File Permissions

UNIX file permissions are the classic real-world example of bit flags. The nine permission bits are split into three groups (owner, group, others), each with read/write/execute sub-bits.

Bit Fields in Structs

C lets you declare struct members with an explicit bit width using ::

c
struct Flags {
    unsigned int read    : 1;
    unsigned int write   : 1;
    unsigned int execute : 1;
    unsigned int padding : 29;
};

Bit fields trade runtime bit manipulation code for a more readable declaration. The compiler handles the masking. Downsides: the layout is implementation-defined (not portable across compilers/architectures), and you cannot take the address of a bit field member. For network protocols use manual masking; for internal configuration flags, bit fields are fine.

Packed Structures

Some compilers support __attribute__((packed)) (GCC/Clang) to eliminate padding between struct members. Combined with bit fields this allows very compact representations, important in embedded systems where RAM is scarce. The tradeoff is potential unaligned access penalties on some architectures.

Code Examples

File Permission Flagsc
#include <stdio.h>

/* UNIX-style permission bit flags */
#define PERM_OWNER_READ    (1 << 8)   /* 0400 */
#define PERM_OWNER_WRITE   (1 << 7)   /* 0200 */
#define PERM_OWNER_EXEC    (1 << 6)   /* 0100 */
#define PERM_GROUP_READ    (1 << 5)   /* 040  */
#define PERM_GROUP_WRITE   (1 << 4)   /* 020  */
#define PERM_GROUP_EXEC    (1 << 3)   /* 010  */
#define PERM_OTHER_READ    (1 << 2)   /* 04   */
#define PERM_OTHER_WRITE   (1 << 1)   /* 02   */
#define PERM_OTHER_EXEC    (1 << 0)   /* 01   */

/* Convenience combinations */
#define PERM_755  (PERM_OWNER_READ | PERM_OWNER_WRITE | PERM_OWNER_EXEC | \
                   PERM_GROUP_READ | PERM_GROUP_EXEC  | \
                   PERM_OTHER_READ | PERM_OTHER_EXEC)

#define PERM_644  (PERM_OWNER_READ | PERM_OWNER_WRITE | \
                   PERM_GROUP_READ | PERM_OTHER_READ)

void print_permissions(unsigned int perms) {
    const char *labels[] = {
        "owner-read", "owner-write", "owner-exec",
        "group-read", "group-write", "group-exec",
        "other-read", "other-write", "other-exec"
    };
    const unsigned int bits[] = {
        PERM_OWNER_READ, PERM_OWNER_WRITE, PERM_OWNER_EXEC,
        PERM_GROUP_READ, PERM_GROUP_WRITE, PERM_GROUP_EXEC,
        PERM_OTHER_READ, PERM_OTHER_WRITE, PERM_OTHER_EXEC
    };
    printf("Permissions (octal %03o):\n", perms);
    for (int i = 0; i < 9; i++) {
        printf("  %-12s: %s\n", labels[i], (perms & bits[i]) ? "YES" : "no");
    }
}

int main(void) {
    unsigned int file_perms = PERM_644;
    print_permissions(file_perms);

    printf("\nAfter granting owner execute:\n");
    file_perms |= PERM_OWNER_EXEC;   /* set */
    print_permissions(file_perms);   /* now 744 */

    printf("\nRevoking group read:\n");
    file_perms &= ~PERM_GROUP_READ;  /* clear */
    print_permissions(file_perms);

    return 0;
}

Each permission flag is a power of 2 (one bit). PERM_644 combines multiple flags with OR. Setting a flag uses |=, clearing uses &= ~FLAG (AND with the complement). The octal format %03o matches traditional UNIX permission notation.

Generic Set/Clear/Toggle/Check Macrosc
#include <stdio.h>

/* Generic bit manipulation macros for any integer type */
#define BIT(n)            (1u << (n))
#define BIT_SET(v, n)     ((v) |=  BIT(n))
#define BIT_CLEAR(v, n)   ((v) &= ~BIT(n))
#define BIT_TOGGLE(v, n)  ((v) ^=  BIT(n))
#define BIT_CHECK(v, n)   (((v) >> (n)) & 1u)

/* Application-level option flags */
#define OPT_VERBOSE   0   /* bit position 0 */
#define OPT_COLOR     1   /* bit position 1 */
#define OPT_RECURSIVE 2   /* bit position 2 */
#define OPT_DRY_RUN   3   /* bit position 3 */

void print_options(unsigned int opts) {
    printf("  verbose=%u  color=%u  recursive=%u  dry-run=%u\n",
           BIT_CHECK(opts, OPT_VERBOSE),
           BIT_CHECK(opts, OPT_COLOR),
           BIT_CHECK(opts, OPT_RECURSIVE),
           BIT_CHECK(opts, OPT_DRY_RUN));
}

int main(void) {
    unsigned int options = 0;

    printf("Initial: ");   print_options(options);

    BIT_SET(options, OPT_VERBOSE);
    BIT_SET(options, OPT_COLOR);
    printf("After setting verbose+color: "); print_options(options);

    BIT_CLEAR(options, OPT_COLOR);
    printf("After clearing color: "); print_options(options);

    BIT_TOGGLE(options, OPT_DRY_RUN);
    printf("After toggling dry-run: "); print_options(options);
    BIT_TOGGLE(options, OPT_DRY_RUN);
    printf("After toggling dry-run again: "); print_options(options);

    /* Test multiple flags at once */
    unsigned int required = BIT(OPT_VERBOSE) | BIT(OPT_RECURSIVE);
    BIT_SET(options, OPT_RECURSIVE);
    int all_set = (options & required) == required;
    printf("\nverbose+recursive both set: %s\n", all_set ? "yes" : "no");

    return 0;
}

BIT(n) computes 1<<n. SET uses OR, CLEAR uses AND-with-complement, TOGGLE uses XOR. BIT_CHECK shifts the flag bit down to position 0 then masks with 1, giving 0 or 1. Testing multiple flags at once uses (opts & mask) == mask.

Bit Fields in Structsc
#include <stdio.h>

/* Bit field struct: compiler allocates exactly the specified number of bits */
struct IPv4Header {
    unsigned int version   : 4;   /* IP version (always 4) */
    unsigned int ihl       : 4;   /* Internet Header Length */
    unsigned int dscp      : 6;   /* Differentiated Services */
    unsigned int ecn       : 2;   /* Explicit Congestion Notification */
    unsigned int tot_len   : 16;  /* Total packet length */
    /* Total: 4+4+6+2+16 = 32 bits = one 32-bit word */
};

/* Simple flags struct */
struct FileMode {
    unsigned int owner_read  : 1;
    unsigned int owner_write : 1;
    unsigned int owner_exec  : 1;
    unsigned int group_read  : 1;
    unsigned int group_write : 1;
    unsigned int group_exec  : 1;
    unsigned int other_read  : 1;
    unsigned int other_write : 1;
    unsigned int other_exec  : 1;
    unsigned int padding     : 23;  /* pad to 32 bits */
};

int main(void) {
    struct IPv4Header hdr = {
        .version = 4,
        .ihl     = 5,   /* 5 * 4 = 20 bytes (no options) */
        .dscp    = 0,
        .ecn     = 0,
        .tot_len = 1500
    };

    printf("IPv4 Header fields:\n");
    printf("  version = %u\n", hdr.version);
    printf("  ihl     = %u (%u bytes)\n", hdr.ihl, hdr.ihl * 4);
    printf("  tot_len = %u bytes\n", hdr.tot_len);
    printf("  sizeof(IPv4Header) = %zu bytes\n", sizeof(hdr));

    struct FileMode mode = {
        .owner_read = 1, .owner_write = 1, .owner_exec = 0,
        .group_read = 1, .group_write = 0, .group_exec = 0,
        .other_read = 1, .other_write = 0, .other_exec = 0
    };

    printf("\nFile mode (644-like):\n");
    printf("  owner: r=%u w=%u x=%u\n",
           mode.owner_read, mode.owner_write, mode.owner_exec);
    printf("  group: r=%u w=%u x=%u\n",
           mode.group_read, mode.group_write, mode.group_exec);
    printf("  other: r=%u w=%u x=%u\n",
           mode.other_read, mode.other_write, mode.other_exec);

    /* Toggle owner execute */
    mode.owner_exec ^= 1;
    printf("After toggling owner_exec: x=%u\n", mode.owner_exec);

    return 0;
}

Bit fields let the compiler pack multiple small values into a single word. The IPv4 header struct maps directly onto the first 32 bits of a real IPv4 packet header. sizeof reports 4 bytes because the four fields sum to exactly 32 bits. The downside is that bit layout is implementation-defined, so you cannot safely cast a raw byte buffer to this struct for network parsing without additional precautions.

Quick Quiz

1. Which operation clears bit 3 of a variable `flags`?

2. How do you check whether ALL flags in a mask `m` are set in `flags`?

3. What is the main portability limitation of bit fields in structs?

Was this lesson helpful?

PreviousNext