Skip to Content
Course content

113: Integer Format Macros with inttypes.h

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

If you've been working with fixed-width types like uint32_t or int64_t from stdint.h, you've probably already hit a wall when it comes to printf. You might have tried using %lu or %llu to print a 64-bit integer, and it worked perfectly on your machine. But the moment you move that code to a different architecture—say, from a 64-bit Linux desktop to an embedded ARM board—your compiler starts screaming warnings about type mismatches, or worse, your output starts printing gibberish.

The Gamble of Assuming Word Sizes

The problem is that uint64_t is a typedef. On some platforms, it's defined as unsigned long; on others, it's unsigned long long. The C standard doesn't mandate which one it must be, only that it must be 64 bits wide. If you hardcode %lu into your format string, you're gambling that the long type on the target system is exactly 64 bits. On Windows 64-bit, for instance, long is often only 32 bits, even though the pointer size is 64. This is a classic "it works on my machine" bug that can haunt you for weeks.

// The naive way: guessing the type
uint64_t bytes_processed = 123456789012345;
printf("Processed %llu bytes\n", bytes_processed); 
// This might be correct here, but it's a portability landmine.

Letting the Compiler Decide with PRI Macros

To solve this, we use inttypes.h. This header provides a set of macros that expand to the correct string literal for the specific platform you're compiling for. Instead of guessing if you need %lu or %llu, you use a macro like PRIu64 (which stands for "Print Right Integer unsigned 64-bit").

The syntax looks a bit strange at first because these macros aren't functions; they are string literals. In C, if you place two string literals next to each other, the compiler automatically concatenates them. That's why you'll see the format string broken up with quotes and commas.

#include <stdio.h>
#include <inttypes.h>

void log_system_stats(uint32_t cpu_load, uint64_t uptime_ms) {
    // We break the string to insert the macro
    printf("CPU Load: %" PRIu32 "%% | Uptime: %" PRIu64 " ms\n", cpu_load, uptime_ms);
}

The Trade-off of Readability

I'll be honest: the syntax is clunky. Writing "%" PRIu64 " ms\n" feels like a step backward compared to a clean "%llu ms\n". You're trading a bit of visual elegance for absolute portability. In my experience, that is a trade you should make every single time. When you're writing professional-grade C, especially for systems programming or cross-platform libraries, "ugly" code that is mathematically guaranteed to be correct is infinitely better than "pretty" code that crashes on a different compiler version.

Just remember the naming convention for these macros so you don't have to look them up every five minutes: PRI (prefix), followed by the sign (d for signed decimal, u for unsigned), and ending with the bit width (8, 16, 32, or 64). If you need to print in hex, just swap the d or u for an x.




📋 Practical Task

Fixing the System Metrics Logger

You have been handed a piece of legacy code used for a hardware monitor. It currently uses hardcoded %d and %llu specifiers, which is causing "format specifies type unsigned long long but the argument has type uint32_t" warnings on the new build server.

Rewrite the print_metrics function to use the correct macros from inttypes.h so that the code is portable across all platforms.

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

void print_metrics(int32_t temperature, uint32_t pressure, uint64_t timestamp) {
    // FIX THIS LINE:
    printf("Temp: %d | Press: %u | Time: %llu\n", temperature, pressure, timestamp);
}

int main() {
    print_metrics(-12, 101325, 1625097600000ULL);
    return 0;
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.