Docs

Bit Fields

Bit Fields in C

Table of Contents

  1. β€’Introduction
  2. β€’Bit Field Declaration
  3. β€’Memory Layout
  4. β€’Accessing Bit Fields
  5. β€’Unnamed Bit Fields
  6. β€’Zero-Width Bit Fields
  7. β€’Practical Applications
  8. β€’Bit Fields with Unions
  9. β€’Portability Considerations
  10. β€’Best Practices
  11. β€’Common Pitfalls
  12. β€’Summary

Introduction

Bit fields are a feature in C that allows you to specify the exact number of bits a structure member should occupy. This enables:

  • β€’Memory optimization: Pack multiple values into single bytes/words
  • β€’Hardware interface: Map to hardware registers exactly
  • β€’Protocol implementation: Match binary protocol formats
  • β€’Flag management: Store multiple boolean flags efficiently

Basic Concept

// Without bit fields: uses at least 12 bytes
struct NormalFlags {
    int isActive;    // 4 bytes for one bit of info
    int isVisible;   // 4 bytes for one bit of info
    int isEnabled;   // 4 bytes for one bit of info
};

// With bit fields: uses only 4 bytes (or less)
struct BitFlags {
    unsigned int isActive  : 1;  // 1 bit
    unsigned int isVisible : 1;  // 1 bit
    unsigned int isEnabled : 1;  // 1 bit
};

Bit Field Declaration

Syntax

struct StructureName {
    type member_name : width;
    type member_name : width;
    // ...
};

Where:

  • β€’type: Must be int, unsigned int, signed int, or _Bool (C99+)
  • β€’member_name: Name of the bit field
  • β€’width: Number of bits (1 to bit-width of type)

Examples

// Boolean flags (1 bit each)
struct Flags {
    unsigned int flag1 : 1;
    unsigned int flag2 : 1;
    unsigned int flag3 : 1;
};

// Small integers
struct Date {
    unsigned int day   : 5;   // 1-31 (needs 5 bits)
    unsigned int month : 4;   // 1-12 (needs 4 bits)
    unsigned int year  : 12;  // 0-4095 (12 bits)
};

// Mixed sizes
struct Packet {
    unsigned int version : 4;
    unsigned int type    : 4;
    unsigned int length  : 16;
    unsigned int flags   : 8;
};

Value Ranges

BitsUnsigned RangeSigned Range
10-1-1 to 0
20-3-2 to 1
30-7-4 to 3
40-15-8 to 7
50-31-16 to 15
80-255-128 to 127
n0 to (2^n - 1)-(2^(n-1)) to (2^(n-1) - 1)

Memory Layout

Packing Within Storage Units

Bit fields are packed into storage units (typically int-sized):

struct Example {
    unsigned int a : 4;   // Bits 0-3
    unsigned int b : 4;   // Bits 4-7
    unsigned int c : 4;   // Bits 8-11
    unsigned int d : 4;   // Bits 12-15
};

Memory (16 bits used, in 32-bit int):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  unused  β”‚ d β”‚ c β”‚ b β”‚ a β”‚
β”‚  16 bits β”‚4b β”‚4b β”‚4b β”‚4b β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Bit:  31...16  15  11   7   3   0

Size Calculation

struct Compact {
    unsigned int a : 3;   // 3 bits
    unsigned int b : 5;   // 5 bits
    unsigned int c : 8;   // 8 bits
};  // Total: 16 bits, fits in one int

sizeof(struct Compact) = 4 bytes (size of int, minimum storage unit)

When Bit Fields Cross Boundaries

struct CrossBoundary {
    unsigned int a : 30;  // 30 bits
    unsigned int b : 10;  // 10 bits - may start new unit!
};

// Implementation-dependent:
// - Some compilers pack b starting at bit 30
// - Others start b in a new storage unit

Accessing Bit Fields

Assignment and Reading

struct Flags {
    unsigned int active  : 1;
    unsigned int visible : 1;
    unsigned int level   : 4;
};

struct Flags f;

// Set values
f.active = 1;
f.visible = 0;
f.level = 7;

// Read values
printf("Active: %u\n", f.active);
printf("Level: %u\n", f.level);

// Values are automatically masked
f.level = 20;  // Only lower 4 bits stored (20 & 0xF = 4)
printf("Level: %u\n", f.level);  // Prints 4, not 20!

Increment and Decrement

struct Counter {
    unsigned int count : 4;  // 0-15
};

struct Counter c = {0};
c.count++;   // Now 1
c.count += 5; // Now 6
c.count = 15;
c.count++;   // Wraps to 0 (overflow)

Comparison

struct Flags f1, f2;

f1.active = 1;
f2.active = 1;

if (f1.active == f2.active) {
    printf("Same active state\n");
}

Unnamed Bit Fields

Purpose

Unnamed bit fields create padding/gaps in the bit layout:

struct Register {
    unsigned int flag1    : 1;
    unsigned int          : 3;   // 3-bit gap (padding)
    unsigned int flag2    : 1;
    unsigned int          : 0;   // Force next field to new unit
    unsigned int data     : 8;
};

Visual Layout

struct Register {
    unsigned int enable   : 1;   // Bit 0
    unsigned int          : 2;   // Bits 1-2 (unused)
    unsigned int mode     : 3;   // Bits 3-5
    unsigned int          : 2;   // Bits 6-7 (unused)
    unsigned int channel  : 4;   // Bits 8-11
};

Layout:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  unused  β”‚ channel β”‚ XX β”‚ mode β”‚ XX β”‚enableβ”‚
β”‚          β”‚   4b    β”‚ 2b β”‚  3b  β”‚ 2b β”‚  1b  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Bit:          11       7    5      2     0

Zero-Width Bit Fields

Purpose

A zero-width bit field forces the next bit field to start at the next storage unit boundary:

struct Aligned {
    unsigned int a : 6;
    unsigned int   : 0;  // Force alignment
    unsigned int b : 6;  // Starts in new storage unit
};

Memory Layout

struct Aligned {
    unsigned int a : 6;   // Bits 0-5 of first int
    unsigned int   : 0;   // Force next unit
    unsigned int b : 6;   // Bits 0-5 of second int
};

sizeof(struct Aligned) = 8 bytes (two ints)

Memory:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   unused   β”‚   a    β”‚   unused   β”‚   b    β”‚
β”‚   26 bits  β”‚ 6 bits β”‚   26 bits  β”‚ 6 bits β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     First int              Second int

Practical Applications

Application 1: Date Storage

// Compact date: only 3 bytes needed (but uses 4 for alignment)
struct CompactDate {
    unsigned int day   : 5;   // 1-31
    unsigned int month : 4;   // 1-12
    unsigned int year  : 11;  // 0-2047
};

struct CompactDate date;
date.day = 25;
date.month = 12;
date.year = 2024;

printf("Date: %u/%u/%u\n", date.month, date.day, date.year);
// Output: 12/25/2024

Application 2: Network Packet Header

// IPv4 header first 32 bits
struct IPv4Header {
    unsigned int version      : 4;
    unsigned int ihl          : 4;   // Internet Header Length
    unsigned int dscp         : 6;   // Differentiated Services
    unsigned int ecn          : 2;   // Explicit Congestion Notification
    unsigned int totalLength  : 16;
};

Application 3: Status Register

struct StatusRegister {
    unsigned int carry      : 1;
    unsigned int zero       : 1;
    unsigned int negative   : 1;
    unsigned int overflow   : 1;
    unsigned int interrupt  : 1;
    unsigned int            : 3;  // Reserved
};

void checkStatus(struct StatusRegister sr) {
    if (sr.zero) printf("Result is zero\n");
    if (sr.negative) printf("Result is negative\n");
    if (sr.overflow) printf("Overflow occurred\n");
}

Application 4: File Permissions

struct FilePermissions {
    unsigned int ownerRead    : 1;
    unsigned int ownerWrite   : 1;
    unsigned int ownerExecute : 1;
    unsigned int groupRead    : 1;
    unsigned int groupWrite   : 1;
    unsigned int groupExecute : 1;
    unsigned int otherRead    : 1;
    unsigned int otherWrite   : 1;
    unsigned int otherExecute : 1;
};

void printPermissions(struct FilePermissions p) {
    printf("%c%c%c%c%c%c%c%c%c\n",
           p.ownerRead    ? 'r' : '-',
           p.ownerWrite   ? 'w' : '-',
           p.ownerExecute ? 'x' : '-',
           p.groupRead    ? 'r' : '-',
           p.groupWrite   ? 'w' : '-',
           p.groupExecute ? 'x' : '-',
           p.otherRead    ? 'r' : '-',
           p.otherWrite   ? 'w' : '-',
           p.otherExecute ? 'x' : '-');
}
// Output: rwxr-xr-x

Bit Fields with Unions

Accessing as Bits and Whole Value

union RegisterAccess {
    unsigned int value;
    struct {
        unsigned int bit0 : 1;
        unsigned int bit1 : 1;
        unsigned int bit2 : 1;
        unsigned int bit3 : 1;
        unsigned int bit4 : 1;
        unsigned int bit5 : 1;
        unsigned int bit6 : 1;
        unsigned int bit7 : 1;
    } bits;
};

union RegisterAccess reg;

// Set individual bits
reg.bits.bit0 = 1;
reg.bits.bit3 = 1;
reg.bits.bit7 = 1;

printf("Value: 0x%02X\n", reg.value);  // 0x89

// Set whole value and read bits
reg.value = 0x55;  // Binary: 01010101
printf("Bit 0: %u\n", reg.bits.bit0);  // 1
printf("Bit 1: %u\n", reg.bits.bit1);  // 0
printf("Bit 2: %u\n", reg.bits.bit2);  // 1

Hardware Register Mapping

union ControlRegister {
    unsigned char value;
    struct {
        unsigned char enable     : 1;
        unsigned char direction  : 1;
        unsigned char speed      : 2;
        unsigned char mode       : 2;
        unsigned char reserved   : 2;
    } fields;
};

// Read from hardware
union ControlRegister ctrl;
ctrl.value = read_hardware_register(0x1234);

// Check fields
if (ctrl.fields.enable) {
    printf("Device enabled, speed=%u, mode=%u\n",
           ctrl.fields.speed, ctrl.fields.mode);
}

// Modify and write back
ctrl.fields.speed = 3;
write_hardware_register(0x1234, ctrl.value);

Portability Considerations

Implementation-Defined Behavior

Several aspects of bit fields are implementation-defined:

  1. β€’Bit ordering: Left-to-right or right-to-left
  2. β€’Storage unit size: May vary by compiler/platform
  3. β€’Signedness of plain int: May be signed or unsigned
  4. β€’Packing across unit boundaries: May or may not split fields

Endianness Issues

struct NetworkHeader {
    unsigned int version : 4;
    unsigned int length  : 4;
};

// On little-endian: version in lower bits
// On big-endian: version in upper bits
// May not match network byte order!

Portable Alternative

// Instead of bit fields for portable code:
#define VERSION_MASK   0x0F
#define VERSION_SHIFT  0
#define LENGTH_MASK    0xF0
#define LENGTH_SHIFT   4

unsigned char header;

// Set version
header = (header & ~VERSION_MASK) | ((version << VERSION_SHIFT) & VERSION_MASK);

// Get version
int version = (header & VERSION_MASK) >> VERSION_SHIFT;

Safe Practices for Portability

// 1. Always use unsigned int for bit fields
struct Good {
    unsigned int field : 4;  // Predictable behavior
};

// 2. Don't rely on specific layout for cross-platform data
// 3. Use explicit byte/bit manipulation for protocols
// 4. Document assumptions about bit ordering

Best Practices

1. Use Unsigned Types

// GOOD: Predictable behavior
struct Flags {
    unsigned int flag : 1;  // 0 or 1
};

// BAD: Sign extension issues
struct BadFlags {
    int flag : 1;  // Could be 0 or -1!
};

2. Match Hardware Documentation

// When mapping to hardware registers, match the datasheet exactly
struct HardwareReg {
    unsigned int enable : 1;  // Bit 0 as per datasheet
    unsigned int        : 3;  // Bits 1-3 reserved
    unsigned int mode   : 4;  // Bits 4-7 as per datasheet
};

3. Consider Alignment

// Group bit fields by storage unit for efficiency
struct Efficient {
    unsigned int a : 8;
    unsigned int b : 8;
    unsigned int c : 8;
    unsigned int d : 8;  // All fit in one 32-bit int
};

4. Document the Layout

/*
 * Status Register Layout:
 * Bit 0:   Enable flag
 * Bit 1:   Ready flag
 * Bits 2-3: Reserved
 * Bits 4-7: Mode selector
 */
struct StatusReg {
    unsigned int enable   : 1;
    unsigned int ready    : 1;
    unsigned int          : 2;
    unsigned int mode     : 4;
};

5. Don't Take Addresses

struct Flags {
    unsigned int a : 4;
    unsigned int b : 4;
};

struct Flags f;
// unsigned int *ptr = &f.a;  // ERROR! Can't take address of bit field
unsigned int *ptr = (unsigned int*)&f;  // OK: address of whole struct

Common Pitfalls

Pitfall 1: Overflow Without Warning

struct Counter {
    unsigned int count : 4;  // Max value: 15
};

struct Counter c;
c.count = 20;  // Silently becomes 4 (20 & 0xF)

Pitfall 2: Sign Extension

struct Signed {
    int value : 4;  // Signed 4-bit: -8 to 7
};

struct Signed s;
s.value = 8;       // Becomes -8 (overflow)
printf("%d\n", s.value);  // Prints -8

Pitfall 3: Assuming Specific Layout

// This structure may have different layouts on different systems!
struct Protocol {
    unsigned int version : 4;
    unsigned int type    : 4;
    unsigned int length  : 16;
};
// Don't use for network protocols or file formats!

Pitfall 4: Width Exceeding Type Size

// ERROR: width larger than type allows
struct Invalid {
    unsigned int value : 64;  // Error if int is 32 bits!
};

Pitfall 5: Using with Arrays or Pointers

struct BitField {
    unsigned int field : 4;
};

struct BitField arr[10];
// Can't create: unsigned int (*p) : 4;  // Invalid syntax

Summary

Key Concepts

ConceptDescription
Bit FieldStructure member with specified bit width
WidthNumber of bits (1 to type bit-width)
PackingBit fields packed into storage units
UnnamedUnnamed bit fields for padding
Zero-WidthForces alignment to next storage unit

Syntax Reference

// Basic declaration
struct Name {
    unsigned int field : width;
};

// With padding
struct Padded {
    unsigned int a : 4;
    unsigned int   : 4;   // 4-bit padding
    unsigned int b : 4;
};

// Force alignment
struct Aligned {
    unsigned int a : 10;
    unsigned int   : 0;   // Next starts on new unit
    unsigned int b : 10;
};

When to Use Bit Fields

βœ… Good use cases:

  • β€’Hardware register mapping (same platform only)
  • β€’Memory-constrained embedded systems
  • β€’Compact internal data structures
  • β€’Flag collections

❌ Avoid for:

  • β€’Cross-platform binary formats
  • β€’Network protocols
  • β€’File formats
  • β€’When you need field addresses

Quick Size Reference

struct Sizes {
    unsigned int a : 1;   // 1 bit:  0-1
    unsigned int b : 4;   // 4 bits: 0-15
    unsigned int c : 8;   // 8 bits: 0-255
    unsigned int d : 16;  // 16 bits: 0-65535
};

Navigation

Bit Fields - C Programming Tutorial | DeepML