Bitfields
Intoducing bitfileds in C++
Table of Contents
- Introduction
- Caveats
- Example
1. Introduction
In C++, bitfields are a feature that allows you to specify the exact number of bits a data member in a structure (or class) should occupy, rather than using a full byte or more as standard data types do. This is useful for memory optimization, especially in embedded systems, low-level programming, or when dealing with hardware registers, network protocols, or packed data structures where you need precise control over memory usage.
What Are Bitfields?
A bitfield is a member of a struct or class declared with a specified number of bits using the : <number>
syntax. Bitfields are typically used with integral types (like int
, unsigned int
, or bool
) and allow you to pack multiple fields into fewer bytes by allocating only the exact number of bits needed.
1 | struct Flags { |
1 | Size of Flags: 2 bytes |
Bitfields can only be declared as Integral Types
Only. Other types such as float
are not allowed. The total size of a struct(or class) with bitfields is usually the underlying type(e.g., 32 bits for an unsigned int) if the the number of bits used doesn’t exceed the bit limit.
When there’s not enough space left in the current allocation unit (like a 16-bit short), the compiler will start a new one, leading to increase size of a struct/class.
1 | struct Flags { |
2. Caveats
2.1 Implementation-Defined Layout
The layout of bitfields (e.g., bit ordering, padding, and alignment) is compiler- and platform-dependent. Different compilers or architectures (e.g., big-endian vs. little-endian) may arrange bits differently, making bitfields non-portable.
2.2 No Type Safety or Overflow Protection
Assigning a value larger than the bitfield silently truncates the value.
1 | struct Flags { |
If we set mode
to be 10
by mistake, which exceeds the representation limit of 3 bits. The compiler silently truncates the value to be 0b1010 & 0b0111 = 0b0010 (which is 2)
2.3 Unspecified Signedness of Plain int Bitfields
1 | struct S { |
The signedness (signed vs. unsigned) of x
is implementation-defined.
Tip: Always use signed int
and unsigned int
explicitly for clarity.
2.4 Compiler Behavior
- Compilers may insert padding between bitfields or at the end of a struct to align data to word boundaries, increasing memory usage unexpectedly.
- Adjacent bitfields may not always be packed tightly; the compiler decides based on the underlying type and alignment rules.
- Accessing bitfields often requires the compiler to generate additional instructions for masking and shifting, which can be slower than accessing regular variables.
- The C++ standard leaves many aspects of bitfields (e.g., allocation order, padding) up to the implementation, so code relying on specific behavior may break when compiled with a different compiler or on a different platform.
3. Example
Real-World Example: IPv4 Header with Bitfields
The IPv4 header is a 20-byte (160-bit) structure (without options) that precedes the payload in an IP packet. It contains fields like version, header length, type of service, total length, identification, flags, fragment offset, time to live, protocol, header checksum, source address, and destination address. Many of these fields are smaller than a byte, making bitfields perfect for mapping them.
1 | struct IPv4Header { |
The total size is 1 + 1 + 2 + 2 + 2 + 1 + 1 + 2 + 4 + 4 = 20 bytes
.