Beyond %d: The Hidden World of printf Formats That Most Devs Never Use
Beyond %d: The Hidden World of printf Formats That Most Devs Never Use
You know what's embarrassing? Copy-pasting chmod 755 from Stack Overflow for the 47th time without knowing what those numbers actually mean. Or printing memory addresses as decimals and wondering why 140732920755216 doesn't tell you jack shit about where your program crashed.
We're all guilty of printf laziness. We learn %d and %s in our CS class and call it a day, while other format specifiers sit there like neglected tools in a garage. Meanwhile, your CPU is literally thinking in binary and hex, and you're forcing it to translate everything to decimal like some kind of numerical middleman.
Time to stop being that person who debugs in the wrong number base. Your sanity depends on it.
The Problem: Debugging in the Wrong Base
So I was working on this bloom filter implementation (yeah, I know, everyone does bloom filters). My hash functions were spitting out numbers and I had no clue what was happening with the collision rates.
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object],(,[object Object],, hash); ,[object Object], ,[object Object],
Then my friend introduced me to hexadecimal printing (chatGPT, they call him 🥴):
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object],(,[object Object],, hash); ,[object Object], ,[object Object],
Suddenly, I could see the actual bit distribution, spot the collision patterns, and fix my hash function. This is why number bases matter. (Also why I should've paid more attention in systems programming 🙃)
The Cast of Characters
%x and %X - Hexadecimal (Base 16)
Not gonna lie, this one changed my debugging game completely. I used to stare at memory addresses like 140732920755216 and think "well, that's a number I guess." Then someone showed me hex(again the same guy) and suddenly 0x7fff5fbff010 actually tells a story.
You'll use this for:
- Memory addresses (finally readable)
- Bit manipulation stuff
- Color codes (now I know why CSS uses #ff0000)
- Hash values that don't look like phone numbers
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], value = ,[object Object],; ,[object Object],(,[object Object],, value); ,[object Object], ,[object Object],(,[object Object],, value); ,[object Object], ,[object Object],(,[object Object],, value); ,[object Object],
Pro tip: Use %#x for automatic 0x prefix. Saves you from typing it every damn time. (Learned this embarrassingly late in my coding journey 😅)
%o and %O - Octal (Base 8)
Okay, real talk - I thought octal was dead until I had to deal with Unix file permissions. Turns out those chmod commands aren't just random numbers you copy from Stack Overflow (guilty as charged 🙋♂️).
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], perms = ,[object Object],; ,[object Object],(,[object Object],, perms); ,[object Object], ,[object Object],(,[object Object],, perms); ,[object Object],
So when you see chmod 755, it's actually:
7 (111 in binary) = owner can read + write + execute 5 (101 in binary) = group can read + execute 5 (101 in binary) = others can read + execute
Makes way more sense than memorizing magic numbers, right? (Though I still Google "chmod permissions" sometimes 🤷♂️)
%b - Binary
Standard C doesn't have this (or maybe it does, go check yourself an tell me in comments), but Go and most modern languages do. This one's clutch when you're doing bit manipulation and your brain hurts from converting everything manually.
gopher@go:~/gogopher@go:~/gogo> [object Object], value := ,[object Object], fmt.Printf(,[object Object],, value) ,[object Object], fmt.Printf(,[object Object],, value) ,[object Object],
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object], value = ,[object Object],; ,[object Object],(,[object Object],, value); ,[object Object],
WARNING
Something's not adding up here. I'll figure this out and update you later... (or drop your findings in the comments if you know what's going on).
When These Actually Matter
Network Debugging
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object], ip = ,[object Object],; ,[object Object], ,[object Object],(,[object Object],, ip); ,[object Object], ,[object Object],(,[object Object],, ip); ,[object Object], ,[object Object],(,[object Object],, ip); ,[object Object],
// Which one screams "192.168.0.1" at you? // The hex one, obviously.
I spent way too long staring at decimal IP representations wondering why my network code was broken. Turns out printing them in hex immediately shows you the byte structure. (Network programming is hard enough without making it harder on yourself 😮💨)
Bit Manipulation
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], permissions = FLAG_READ | FLAG_WRITE; ,[object Object],(,[object Object],, permissions); ,[object Object], ,[object Object],(,[object Object],, permissions); ,[object Object],
When you're doing bitwise operations, seeing the actual bits saves your sanity. Trust me on this one. (Or don't, and spend hours debugging like I did 🤡)
Figuring Out Endianness (Because Your CPU Might Be Backwards)
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object], test = ,[object Object],; ,[object Object], *bytes = (,[object Object],*)&test; ,[object Object],(,[object Object],, test); ,[object Object],(,[object Object], i = ,[object Object],; i < ,[object Object],; i++) { ,[object Object],(,[object Object],, i, bytes[i]); } ,[object Object], ,[object Object],
This saved my ass when I was doing network programming and couldn't figure out why my packets were garbage. Turns out network byte order is big-endian, but my machine was little-endian. Who knew? (bluffing 🙃)
Other Formatting Tricks Worth Knowing
Making Your Output Look Less Like Garbage
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], val = ,[object Object],; ,[object Object],(,[object Object],, val); ,[object Object], ,[object Object],(,[object Object],, val); ,[object Object], ,[object Object],(,[object Object],, val); ,[object Object], ,[object Object],(,[object Object],, val); ,[object Object],
Zero-padding is clutch when you're printing memory dumps or want things to line up nicely. Makes your output look professional instead of like a toddler's finger painting.
Floating Point
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], pi = ,[object Object],; ,[object Object],(,[object Object],, pi); ,[object Object], ,[object Object],(,[object Object],, pi); ,[object Object], ,[object Object],(,[object Object],, pi); ,[object Object],
Scientific notation is great when you're dealing with really big or really small numbers and don't want to count zeros like some kind of human calculator.
Different Languages, Same Concept
Go's Extended Family
gopher@go:~/gogopher@go:~/gogo> [object Object], fmt.Printf(,[object Object],, ,[object Object],) ,[object Object], fmt.Printf(,[object Object],, ,[object Object],) ,[object Object], fmt.Printf(,[object Object],, ,[object Object],) ,[object Object], fmt.Printf(,[object Object],, ,[object Object],) ,[object Object],
That %T one is super handy when you're debugging type issues and can't figure out what the hell Go thinks your variable is. (Go's type system can be... opinionated 😤)
Python's f-string
python@python:~/pythonpython@python:~/python>>> value = ,[object Object], ,[object Object],(,[object Object],) ,[object Object], ,[object Object],(,[object Object],) ,[object Object], ,[object Object],(,[object Object],) ,[object Object], ,[object Object],(,[object Object],) ,[object Object], ,[object Object],(,[object Object],) ,[object Object],
Python's f-strings are honestly way cleaner than printf. Fight me. (But also, printf is everywhere so learn it anyway)
The Unicode
Here's where shit gets weird. Ever wondered how your computer stores "🚀" vs "A"? Spoiler alert: it's more complicated than you think.
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object], ascii = ,[object Object],; ,[object Object],(,[object Object],, ascii, ascii); ,[object Object], ,[object Object], ,[object Object],
UTF-8 encoding (the thing that makes the internet work):
- ASCII stuff (0-127): 1 byte
- European languages, Arabic, etc.: 2 bytes
- Chinese, Japanese, most emoji: 3 bytes
- Weird symbols, more emoji: 4 bytes
TIP
Random fact: UTF-8 was created by Rob Pike and Ken Thompson - the same guys who made Go. Pretty cool legacy.
Why This Actually Matters for Performance
Look, different number bases aren't just for showing off. They map to how computers actually work:
- Hexadecimal: Each digit = exactly 4 bits. Perfect for memory dumps.
- Octal: Each digit = exactly 3 bits. Compact for certain operations.
- Binary: What your CPU actually sees. No translation needed.
When you're doing low-level stuff, thinking in hex or binary can save you from making dumb mistakes. (Trust me, I've made plenty 🤦♂️)
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object], flags = ,[object Object],; ,[object Object], ,[object Object], mask = ,[object Object],; ,[object Object], ,[object Object],(,[object Object],, flags, flags); ,[object Object], ,[object Object],(,[object Object],, mask, mask); ,[object Object], ,[object Object],(,[object Object],, flags & mask, flags & mask);
Plot twist: Remember when I said standard C doesn't have %b? Well, turns out I was wrong (kinda). Some compilers like GCC actually support %b as an extension, even though it's not in the official C standard. So if you're using GCC, you might get lucky. Other compilers? You're on your own.
This is why testing on different systems is important, kids. What works on your machine might not work everywhere. (Learning this the hard way builds character, apparently 😅)
Start Using These Tomorrow
Next time you're debugging, try this:
- Use %x for memory addresses - way easier to read
- Use %o for file permissions - finally understand chmod
- Use %b for bit operations - see what's actually happening
- Print values in multiple bases - sometimes one just clicks
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], mystery_value = get_mysterious_value(); ,[object Object],(,[object Object],, mystery_value, mystery_value, mystery_value);
Do this once and you'll never go back to staring at random decimal numbers. (Well, mostly. Old habits die hard 🙃)
Bottom Line
Stop limiting yourself to %d. These aren't just academic bullshit - they're tools that'll actually make your debugging life easier. Plus, using hex makes you look like you know what you're doing in code reviews. (Fake it till you make it, right? 😏)
Your 3 AM debugging self will thank you later. (And probably curse you less often too)
Got any other printf tricks that saved your ass? Let me know. To the comments now. brrrrr...
If you are here, leave a reaction bruh, takes nothing except a github account.
Nishant Gaurav
Full Stack Developer
Related Posts
Write your own HTTP server from scratch using C
Learn how to build a complete HTTP server from scratch using C programming. Understand sockets, threading, and network programming fundamentals.
AI-First Development with Cursor
How AI-powered code editors like Cursor are revolutionizing the development workflow in 2025.