C Notes
Master bit fields in C programming with practical examples covering struct bit fields, memory optimization, hardware registers, protocol headers, and limitations.
Bit fields allow you to specify the exact number of bits a struct member should occupy. Instead of wasting an entire byte or integer for a flag that only needs one bit, you can pack multiple small values into a single machine word. This technique is essential in embedded systems, network protocol implementations, and hardware register access.
What Are Bit Fields?
A bit field is a struct member whose width is specified in bits rather than bytes. The compiler packs these fields together, saving memory when you need to store many small values.
#include <stdio.h>
struct Flags {
unsigned int bold : 1; // 1 bit
unsigned int italic : 1; // 1 bit
unsigned int underline: 1; // 1 bit
unsigned int font_size: 5; // 5 bits (values 0-31)
};
int main() {
struct Flags f = {1, 0, 1, 14};
printf("Size of struct: %zu bytes\n", sizeof(struct Flags));
printf("Bold: %u, Italic: %u, Underline: %u, Font size: %u\n",
f.bold, f.italic, f.underline, f.font_size);
return 0;
}Size of struct: 4 bytes Bold: 1, Italic: 0, Underline: 1, Font size: 14
Without bit fields, storing these four values separately would require 16 bytes (4 ints). With bit fields, everything fits in 4 bytes.
Bit Field Syntax
struct {
type member_name : width;
};Rules for bit fields:
typemust beint,unsigned int,signed int, or_Bool(C99+)widthcannot exceed the bit width of the type- Width of 0 forces alignment to the next storage unit boundary
- You cannot take the address of a bit field member
Practical Example: Date Storage
Storing a date normally requires 12 bytes (3 ints). With bit fields, we can compress it significantly:
#include <stdio.h>
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 (needs 12 bits)
};
int main() {
struct Date today = {13, 6, 2025};
printf("Size: %zu bytes\n", sizeof(struct Date));
printf("Date: %u/%u/%u\n", today.day, today.month, today.year);
// Modify fields
today.day = 25;
today.month = 12;
printf("Modified: %u/%u/%u\n", today.day, today.month, today.year);
return 0;
}Size: 4 bytes Date: 13/6/2025 Modified: 25/12/2025
Network Protocol Headers
Bit fields are perfect for defining packet headers. Here's a simplified TCP header:
#include <stdio.h>
#include <stdint.h>
struct TCPHeader {
uint16_t src_port; // 16 bits
uint16_t dest_port; // 16 bits
uint32_t seq_number; // 32 bits
uint32_t ack_number; // 32 bits
unsigned int data_offset: 4; // 4 bits
unsigned int reserved : 3; // 3 bits
unsigned int ns : 1; // 1 bit
unsigned int cwr : 1; // 1 bit
unsigned int ece : 1; // 1 bit
unsigned int urg : 1; // 1 bit
unsigned int ack : 1; // 1 bit
unsigned int psh : 1; // 1 bit
unsigned int rst : 1; // 1 bit
unsigned int syn : 1; // 1 bit
unsigned int fin : 1; // 1 bit
uint16_t window_size; // 16 bits
uint16_t checksum; // 16 bits
uint16_t urgent_pointer; // 16 bits
};
int main() {
struct TCPHeader header = {0};
header.src_port = 8080;
header.dest_port = 443;
header.syn = 1;
header.data_offset = 5;
header.window_size = 65535;
printf("TCP Header size: %zu bytes\n", sizeof(struct TCPHeader));
printf("Source Port: %u\n", header.src_port);
printf("Dest Port: %u\n", header.dest_port);
printf("SYN flag: %u\n", header.syn);
printf("Window: %u\n", header.window_size);
return 0;
}TCP Header size: 20 bytes Source Port: 8080 Dest Port: 443 SYN flag: 1 Window: 65535
Hardware Register Access
In embedded programming, hardware registers often pack multiple controls into a single register:
#include <stdio.h>
// Simulated UART control register
struct UART_Control {
unsigned int baud_rate : 4; // 0-15 (represents baud rates)
unsigned int data_bits : 2; // 0=5bits, 1=6bits, 2=7bits, 3=8bits
unsigned int stop_bits : 1; // 0=1bit, 1=2bits
unsigned int parity : 2; // 0=none, 1=odd, 2=even
unsigned int flow_control: 1; // 0=off, 1=on
unsigned int tx_enable : 1; // Transmitter enable
unsigned int rx_enable : 1; // Receiver enable
unsigned int reserved : 4; // Reserved bits
};
int main() {
struct UART_Control uart = {0};
// Configure: 9600 baud, 8 data bits, 1 stop bit, no parity
uart.baud_rate = 7; // Index for 9600
uart.data_bits = 3; // 8 bits
uart.stop_bits = 0; // 1 stop bit
uart.parity = 0; // No parity
uart.tx_enable = 1;
uart.rx_enable = 1;
printf("UART Register size: %zu bytes\n", sizeof(uart));
printf("TX Enabled: %u, RX Enabled: %u\n", uart.tx_enable, uart.rx_enable);
return 0;
}UART Register size: 4 bytes TX Enabled: 1, RX Enabled: 1
Unnamed Bit Fields and Padding
You can use unnamed bit fields for padding and zero-width bit fields to force alignment:
#include <stdio.h>
struct Packed {
unsigned int a : 3;
unsigned int : 2; // 2-bit padding (unnamed)
unsigned int b : 5;
unsigned int : 0; // Force next field to new storage unit
unsigned int c : 4;
};
int main() {
printf("Size: %zu bytes\n", sizeof(struct Packed));
struct Packed p = {5, 20, 9};
printf("a=%u, b=%u, c=%u\n", p.a, p.b, p.c);
return 0;
}Size: 8 bytes a=5, b=20, c=9
Bit Fields vs Manual Bit Manipulation
| Feature | Bit Fields | Manual Masking |
|---|---|---|
| Readability | High | Low |
| Portability | Limited | Full control |
| Address-of | Not allowed | Possible on container |
| Bit order | Implementation-defined | You decide |
| Performance | Compiler-optimized | Your responsibility |
| Array of | Not possible | Use manual masking |
Limitations of Bit Fields
- Cannot take address –
&structure.bitfieldis illegal - Cannot use with arrays – you can't have
int arr[5] : 3 - Implementation-defined ordering – bit order may vary across compilers
- Portability issues – padding and alignment rules differ
- Cannot use with
scanf()– need to read into a temp variable first
#include <stdio.h>
struct Example {
unsigned int x : 4;
unsigned int y : 4;
};
int main() {
struct Example e;
int temp;
// Cannot do: scanf("%d", &e.x); // ERROR!
// Correct approach:
printf("Enter x (0-15): ");
scanf("%d", &temp);
e.x = temp;
printf("x = %u\n", e.x);
return 0;
}Signed vs Unsigned Bit Fields
Be careful with signed bit fields – the sign bit takes one position:
#include <stdio.h>
struct SignTest {
int signed_val : 3; // Range: -4 to 3
unsigned int unsigned_val : 3; // Range: 0 to 7
};
int main() {
struct SignTest s;
s.signed_val = 3; // Max positive for 3-bit signed
s.unsigned_val = 7; // Max for 3-bit unsigned
printf("Signed: %d\n", s.signed_val);
printf("Unsigned: %u\n", s.unsigned_val);
s.signed_val = 5; // Overflow! 5 doesn't fit in 3-bit signed
printf("Signed overflow (5): %d\n", s.signed_val); // Implementation-defined
return 0;
}Signed: 3 Unsigned: 7 Signed overflow (5): -3
Interview Questions on Bit Fields
Q1: Can you create an array of bit fields? No, bit fields cannot be arrays. You can have a struct containing bit fields, and create an array of those structs.
Q2: What does a zero-width bit field do? A zero-width unnamed bit field forces the next bit field to start at the beginning of the next storage unit boundary.
Q3: Are bit fields portable across different systems? No. The order of bit allocation (MSB vs LSB first), padding, and alignment are implementation-defined. For portable code, use manual bit masking.
Q4: Can bit fields be used with unions? Yes, and this is a common technique for accessing individual bits and the whole value simultaneously.
Q5: What is the maximum width a bit field can have? The width cannot exceed the number of bits in the declared type. For unsigned int on most systems, that's 32 bits.
Summary
Bit fields in C provide a convenient way to pack multiple small values into minimal memory. They're ideal for representing hardware registers, protocol headers, and flag collections. However, they come with portability limitations since bit ordering is implementation-defined. For embedded systems and performance-critical code where you control the compiler, bit fields offer readable syntax. For cross-platform networking code, manual bit manipulation with explicit masks is often preferred.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Bit Fields in C – Definition, Use Cases, and Protocol Headers.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this C Programming topic.
Search Terms
c-programming, c programming, programming, advanced, bit, fields, bit fields in c – definition, use cases, and protocol headers
Related C Programming Topics