Skip to Content
Course content

112: Exact-Width Integers with stdint.h

Click on the "Edit" button in the top corner of the screen to edit your slide content.

One of the most frustrating things about C is that the language doesn't actually tell you how big an int is. It just says it's "at least 16 bits." Depending on whether you're compiling for an Arduino, a 32-bit ARM chip, or a 64-bit Windows machine, your int might be 2 bytes or 4 bytes. In a vacuum, that doesn't matter. But the moment you start reading data from a file or a network socket, it's a disaster.

I've spent way too many hours debugging "ghost" bugs caused by platform-dependent integer sizes. To avoid this, we use stdint.h. It gives us exact-width types—types that are guaranteed to be the same size regardless of where the code is running.

Defining a Binary Sensor Packet

Let's imagine we're writing a driver for a piece of hardware—say, a digital temperature sensor. The sensor sends us a "packet" of 7 bytes. The first two bytes are the device ID, the next four are a timestamp, and the last byte is the temperature reading. If we get the sizes wrong, we'll read the wrong bytes, and our temperature will look like a random, massive number.

Here is how I'd start sketching out the structure for this packet:

#include <stdio.h>
#include <stdint.h>

typedef struct {
    uint16_t device_id;  // Exactly 16 bits
    uint32_t timestamp;  // Exactly 32 bits
    uint8_t reading;     // Exactly 8 bits
} SensorPacket;

Notice I used uint (unsigned integer) followed by the bit count. uint16_t is exactly 16 bits. This is my safety blanket. I know exactly how many bytes I'm dealing with.

The Trap of Platform-Dependent Types

Now, let's say I'm feeling lazy. I might think, "A timestamp is just a number, I'll just use a standard unsigned int." I've made this mistake plenty of times in my early career.

// This is the "danger" version
typedef struct {
    uint16_t device_id;
    unsigned int timestamp; // Danger! Size varies by platform
    uint8_t reading;
} SensorPacket;

On my development machine, unsigned int is 4 bytes. Everything looks great. But then I push this code to a small 16-bit microcontroller. Suddenly, unsigned int becomes 2 bytes. The reading byte is now shifted 2 positions to the left in memory. When the program tries to read the temperature, it reads part of the timestamp instead. The code compiles perfectly, but the data is garbage.

Locking Down the Widths

To fix this, I have to be disciplined. I go back to uint32_t. By using the _t types from stdint.h, I'm telling the compiler: "I don't care what the native word size of this CPU is; I need exactly 32 bits here."

Let's put it all together into a small program that simulates receiving this binary data. I'll use a byte array to represent the raw data coming off the wire and cast it to our struct to see if we've aligned things correctly.

#include <stdio.h>
#include <stdint.h&gt>

typedef struct {
    uint16_t device_id;
    uint32_t timestamp;
    uint8_t reading;
} SensorPacket;

int main() {
    // Simulating 7 bytes of raw data from a sensor
    // ID: 0x0102, Timestamp: 0x0000000A, Reading: 0x19 (25C)
    uint8_t raw_data[] = {0x02, 0x01, 0x0A, 0x00, 0x00, 0x00, 0x19};

    // Point our struct pointer to the raw data
    SensorPacket *packet = (SensorPacket *)raw_data;

    printf("Device ID: %u\n", packet->device_id);
    printf("Timestamp: %u\n", packet->timestamp);
    printf("Temperature: %u C\n", packet->reading);

    return 0;
}

One quick side note: in a real-world production environment, you'd also have to worry about "padding" and "endianness" (the order of bytes), but for now, the most important habit you can form is refusing to use int or long when the specific bit-width of your data matters. If it's for a counter that will never exceed 255, use uint8_t. If it's for a memory address, use uintptr_t. Be explicit.




📋 Practical Task

Exercise: Building a Network Packet Header Parser

You are tasked with writing a program that parses a custom network header. The header is exactly 8 bytes long and consists of the following fields:

  • Version: 8 bits (unsigned)
  • Payload Length: 16 bits (unsigned)
  • Sequence Number: 32 bits (unsigned)
  • Checksum: 8 bits (unsigned)

Write a program that defines a struct using the correct stdint.h types to represent this header. Then, create a uint8_t` array containing the following hexadecimal values: {0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x05, 0xAF}. Cast this array to your struct and print each field to the console to verify the data was parsed correctly.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.