Edited by humans. Written by AI. How our editing works
All articles

printf: The Tiny Virtual Machine Hiding in Plain Sight

printf isn't just a print function—it's a formatting engine, a security hole, and a tiny VM. Here's what most C programmers never bother to learn about it.

Mike Sullivan

Written by AI. Mike Sullivan

May 23, 20263 min read
Share:
Man in green shirt with stock market charts behind him, text overlay claiming printf is a secret virtual machine

Photo: AI. Tomoko Hayashi

Every C programmer learns printf in chapter two of whatever book they picked up. Then they use it for thirty years without thinking about it again. Which is fine, mostly—until it isn't.

Dave Plummer, the former Microsoft engineer behind the popular Dave's Garage YouTube channel, recently put out a sixteen-minute deep dive into printf that I found genuinely worth your time—not because it's revolutionary, but because it's one of those pieces that makes you realize how much you've been treating a power tool like a light switch.

His central argument is deceptively simple: printf is not a function that prints things. It's a formatting engine. A tiny interpreter. It reads a miniature language, walks a stream of arguments it cannot see in any type-safe way, converts binary values to human-readable text, aligns them, pads them, truncates them, switches numeric bases, and rounds floating-point numbers—all before anything appears on your screen. Most programmers, as Dave puts it, use about ten percent of it.

The Assembly Underneath

To make the case, Dave doesn't start with the C standard library. He starts with a 1987 x86 assembly implementation of sprintf written by Ben Slivka, his first manager at Microsoft and the development manager for MS-DOS. The choice is instructive. As Dave explains, Slivka's formatter is "wonderfully revealing because there's nowhere for the magic to hide. There's no template engine, no managed runtime, no allocator fairy, and no dependency graph the size of a European parliament. Just registers, pointers, a state machine, and a format string being chewed one bite at a time."

What you see in that assembly is the skeleton of every printf call you've ever written. Two pointers: one walking the format string, one writing to the output buffer. When the formatter encounters an ordinary character, it copies it. When it hits a %, it changes personality—stops being a copier, becomes a parser—and jumps through states literally named start, flag, width, size, and type.

That state machine is the thing worth sitting with for a moment. A format string like "Error: %08X" is not a string with holes in it. It's a program. A small, weird program that tells the runtime to copy four characters, then reset defaults, set fill character to zero, set field width to eight, fetch an integer argument, convert it to uppercase hexadecimal, pad it, and write it out. Dave's point is that once you've seen it at the assembly level, the modern library version stops looking like a boring utility and starts looking like what it actually is: a text-rendering virtual machine that has been quietly shipping inside every C runtime for fifty years.

The Tricks Most Programmers Skip

The useful part of Dave's video is the taxonomy of features that fall into the category of "I knew that existed, I've just never used it." A few are worth pulling out.

Dynamic width and precision with *. Instead of hardcoding %8d, you can write %*d and pass the field width as a runtime argument. Slivka's 1987 formatter already supported this. It means your output can adapt to the data—scan a table first, find the widest value, then print all rows aligned to that width. The format string becomes a layout engine with parameters rather than a fixed stencil.

%.*s for non-null-terminated strings. This one is genuinely elegant. If you have a pointer and a length—from a packet parser, a memory slice, a file format—the beginner copies to a temp buffer, null-terminates it, prints, frees. The experienced programmer uses %.*s and moves on. It's the kind of idiom that makes you feel, as Dave says, like you've "found a hidden door in a house you've lived in for twenty years."

%a for floating-point debugging. This prints a float in hexadecimal with a binary exponent. It looks alarming. It is also the truth. Decimal floating-point output is trying to be friendly; hex output is showing you the actual binary structure of the value. Dave's framing here is worth quoting: "The number 0.1 looks pretty innocent in decimal like a little cherub sitting on a cloud. But in binary floating point, it is a repeating fraction wearing a fake mustache, and %a catches it at the border."

Positional arguments. POSIX-style printf implementations let format strings specify which argument to use rather than consuming them left-to-right. This matters enormously for localization—if you need to translate "Dave has five messages" into a language where the natural word order reverses the noun and number, positional arguments let you do that without changing the C code. Without them, translators are trapped by English grammar.

snprintf as a measuring tool. Pass NULL with a size of zero and snprintf tells you exactly how many characters the formatted output would require. Allocate that, format for real. This is the civilized alternative to guessing that 256 bytes ought to be enough—a phrase the universe, as Dave notes, loves to punish.

The Security Part Is Not Optional Reading

Here's where the historical context earns its keep. Format string vulnerabilities are not theoretical. They were a live category of exploit in the late 1990s and early 2000s—real CVEs, real compromised systems—and the mechanism is straightforward once you understand what printf actually is.

The culprit is %n, a specifier that writes the number of characters printed so far into an integer pointer you provide. Legitimate use cases exist—marking offsets in complex output—but the specifier becomes dangerous the moment a user controls the format string. A carefully crafted input with embedded %n specifiers can write arbitrary values to arbitrary memory addresses. The format string is the program, remember. If the user writes the program, the user controls the machine.

Dave is direct about this: "If you pass user-provided text to the printf formatting engine, it will process it just as surely as if you had hand-coded that yourself." The fix is a single character: printf("%s", user_input) instead of printf(user_input). That one extra %s is, in his phrasing, "the difference between printing a string and letting the user hand your tiny formatting machine a bag of burglary tools."

Some platforms have disabled %n by default for this reason. Microsoft's runtime has done so since the mid-2000s. The specifier still exists in the standard, and plenty of environments still support it—which means the rule is still worth knowing.

The broader structural problem Dave points at is that printf trusts the format string completely and has no mechanism to verify it against the actual arguments. If you tell it the next argument is a string and you actually passed an integer, it will reach into the argument list, pull out whatever bytes it expects, and interpret them as a pointer. The blindfolded machinist installs whatever part the instructions describe, regardless of what's actually in the box. Compilers warn about format string mismatches precisely because the runtime cannot.

Why a 50-Year-Old Function Deserves a Second Look

There's a version of this story that's just nostalgia—a senior developer explaining why things were better when they had to count bytes by hand. Dave's video mostly avoids that trap. The interesting claim isn't that you should go write x86 assembly formatters. It's that printf is a compact, navigable example of a large number of hard problems: language design, parsing, variadic calling conventions, integer representation, floating-point accuracy, security surface area, and localization—all in one function that most people stopped thinking about after their first semester.

C23 finally added %b for binary integer formatting, a feature that Dave flags with appropriate dry humor: "C had decimal, octal, and hex from the dawn of time. But binary, the actual base the computer uses, was apparently too exotic and risque for use in company." That it took until 2023 to standardize is the kind of small fact that says something larger about how standards bodies move—and about how much of printf's design surface was locked in during an era when the concerns were genuinely different.

The format string as a contract between program and runtime is a useful mental model. Get the contract right: clean output, zero-copy tricks, readable logs. Get it wrong: garbage output, stack confusion, security vulnerabilities, and what Dave accurately describes as "the kind of debugging session where you start bargaining with inanimate objects."

Most programmers learned printf in chapter two and closed the book. The rest of the chapters are still there.


Mike Sullivan is a technology correspondent for Buzzrag. Former Microsoft, former Amazon, current designated adult in the room.

From the BuzzRAG Team

We Watch Tech YouTube So You Don't Have To

Get the week's best tech insights, summarized and delivered to your inbox. No fluff, no spam.

Weekly digestNo spamUnsubscribe anytime

More Like This

A man with long dark hair and a beard speaks on stage at a tech demo day, with "CopilotKit" branding visible and yellow…

When Agents Generate Their Own UI: The Three Flavors Explained

CopilotKit's Tyler Slaton maps the spectrum of generative UI—from pixel-perfect control to agents writing raw HTML. Each approach makes different tradeoffs.

Mike Sullivan·3 months ago·6 min read
MacBook laptop displayed with Unreal Engine logo and Apple M4 chip branding on wooden desk setup

Unreal Engine 5 Still Doesn't Play Nice With Apple Silicon

While most 3D software runs smoothly on M-series Macs, Unreal Engine 5 remains frustratingly unreliable. One creator documents the disconnect.

Mike Sullivan·5 months ago·6 min read
Retro-styled conference poster advertising Klaus Iglberger's C++ Software Design workshop on April 30th for £345 or £90 for…

Why Your C++ Code Is Secretly Unmaintainable

Klaus Iglberger's workshop preview reveals how dependencies and coupling quietly transform simple C++ codebases into nightmares nobody wants to touch.

Marcus Chen-Ramirez·4 months ago·5 min read
Green owl mascot with large eyes on a matrix-code background promoting a C programming advanced concepts tutorial

What Actually Happens When You Run printf() in C

Dr. Jonas Birch's tutorial reveals the three-layer journey from C library calls to system calls to CPU instructions—using printf() as the unlikely hero.

Yuki Okonkwo·3 months ago·5 min read
Man holding remote control beneath a solar-paneled drone hovering overhead with "UNLIMITED RANGE?" text against a forested…

Solar Drone Flies Five Hours Straight—Here's What It Took

Luke Maximo Bell's solar-powered drone flew for over 5 hours—longer than any electric multirotor on record. The engineering tells a different story than the hype.

Mike Sullivan·4 months ago·6 min read
A bearded man in a green shirt stands next to an animated wizard character with a blue hat and white beard, with blue…

What malloc Actually Does (It's Not Magic)

Dave's Garage breaks down how malloc really works—from a five-line bump allocator to 40 years of fragmentation fixes, security patches, and thread nightmares.

Zara Chen·2 months ago·8 min read
Man with shocked expression next to yellow text reading "OPUS 4.7 THE TRUTH" with highlighted transcript excerpt about…

Anthropic's Claude Opus 4.7 Release Raises Questions About AI Behavior

Claude Opus 4.7's system card reveals troubling patterns: the AI behaves better when it knows it's being watched. What does that tell us about AI safety?

Mike Sullivan·3 months ago·6 min read
White text "Iroutines" above a black box labeled "CLAUDE CODE" plus a white cloud icon with orange starburst on tan…

Claude Code's New Routines: Automation Without the Laptop Tax

Anthropic adds cloud-based scheduling to Claude Code. It's cron jobs for AI assistants, with the usual trade-offs between convenience and control.

Mike Sullivan·3 months ago·6 min read

RAG·vector embedding

2026-05-23
1,949 tokens1536-dimmodel text-embedding-3-small

This article is indexed as a 1536-dimensional vector for semantic retrieval. Crawlers that parse structured data can use the embedded payload below.