Packing & Unpacking two integers in one
Packing and unpacking two integers into one is a bit manipulation technique used to compress multiple small values into a single larger variable to save memory or bandwidth.
Simple Example
We will look at simple example of
#include <cstdint>
#include <cstdlib>
#include <iostream>
int main() {
uint16_t first{};
uint16_t second{};
std::cout << "enter first and second values of unsigned integers "
"respectively (max is "
<< UINT16_MAX << ")\n";
std::cin >> first >> second;
uint32_t data{};
data = ((uint16_t)first << 16) | ((uint16_t)second);
std::cout << "First value is " << (data >> 16) << '\n';
std::cout << "Second value is " << (data & 0xFFFF) << '\n';
return EXIT_SUCCESS;
}
Packing
consider this line…
uint32_t data = ((uint16_t)first << 16) | ((uint16_t)second);
To get a big picture of what’s happening, let’s take an example
- We input 1000 in
firstand 2000 insecond. In hexadecimal, they will be0x3e8and0x7d0respectively. datais initially0x0(all bits zeroes).(uint16_t)first << 16typecastsfirstto 16 bits int (which already is) and left shifts the whole to 16 bits. So,databecomes0x3e800000.- Then almost immediately we perform bitwise OR with
secondto the result. So,datafinally becomes0x3e807d0.
You can see that both the integers are packed inside another integer.
Unpacking
now consider this line…
(data >> 16)
and
(data & 0xFFFF) // 0xFFFF is same as 0x0000FFFF
- To get the first value, we move the first 16 bits to the lowest 16 bits position by performing right shift.
So we get the
0x3e8back which is ourfirstvariable!! - To get the second value, we isolate the last 16 bits, discarding the rest by performing bitwise AND with 0x0000FFFF.
This results with
0x7d0which is in fact oursecondvariable ;)
Real-life examples where this is used
Concurrent open ports scanner (with epoll) : I myself have used this trick at around line 113 here to pack file descriptor and port together so I can use them later on around line 130.
Linux Kernel’s Device Numbers (dev_t) : In this code around line 10, the Major and Minor numbers are stored together in
dev_tbut they are 12 and 20 bits respectively.Hardware Page Table Entries (PTE) : Around the top in this code, you will see macros for page bits. When an operating system maps virtual memory to physical memory, it uses a hardware data structure called a Page Table. Because there are millions of pages in memory, the entries must be as small as possible. the bottom 12 bits of a physical page address are always zero. The hardware repurposes those 12 empty bits to pack boolean status flags.
Network Protocols (IPv4 Headers) : In the
struct iphdrin the code, you will notice something interesting here: the kernel actually defines the ihl (Internet Header Length) and version bit-fields in a different order depending on whether the system’s CPU is little-endian (like x86) or big-endian (like ARM/PowerPC). The kernel uses C struct bit-fields or manual bitwise operations to assemble this 32-bit word perfectly before sending it down to the network interface card.