Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Printf in C: Why Different Output on Commenting a Line?

Why does printf give different output in C when one line is commented? Understand how argument types affect register usage and create undefined behavior.
Confused C developer debugging unexpected printf output caused by a commented line in code, highlighting undefined behavior Confused C developer debugging unexpected printf output caused by a commented line in code, highlighting undefined behavior
  • ⚠️ Format specifier mismatches in printf lead to undefined behavior in C.
  • 🧠 Commenting unrelated code can shift register allocations, changing printf output.
  • 💻 Compiler optimizations can expose or hide bugs caused by variadic functions.
  • 🔍 Tools like -Wformat=2, Valgrind, and UBSan help detect risky printf usage.
  • 📉 Variadic functions remove type safety, so you must be careful to get things right.

Why printf in C Gives Different Output When You Comment a Line

If you've ever seen printf in C produce very different results, or even crash, just after you comment out a line of code that seems unrelated, you are seeing undefined behavior happen. This happens because of variadic functions, calling conventions, format specifiers, and how compilers give registers or stack space to variables. Here, we will break down how printf works. We will also look at the dangers of type mismatches. And we will show how even a small change in code can greatly change how the program acts.


How printf Works in C

Understanding printf as a Variadic Function

In C, printf is declared as a variadic function:

int printf(const char *format, ...);

This means it takes a changing number of arguments after the format string. The format string has placeholders (called format specifiers), like %d, %f, and %s. These tell the program how to read and show the next arguments.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Here’s a simple example:

int a = 10;
printf("Value: %d\n", a);

This will correctly print:

Value: 10

Why Format Strings Matter

The format string is not just for people. It tells printf how to figure out the list of values that come after it. Variadic functions like printf are not type-safe. This means the compiler does not check the types of arguments past the fixed ones (the format string itself).

If your format specifier does not match the actual data type of the argument, problems start fast.


Common C Format Specifiers

Knowing common format specifiers is key for using printf safely:

Specifier Meaning Expected Type
%d Signed integer int
%u Unsigned integer unsigned int
%f Floating-point number double
%lf Double precision float double
%s Null-terminated string char*
%c Single character int (promoted char)
%p Pointer void*
%ld Long integer long

If you mix types, for example, using %d for a float, you will cause undefined behavior. This can look like garbage output, crashes, or different actions based on other variables in your code.


Commenting Code Alters Register Allocation

Consider this C program:

#include <stdio.h>

int main() {
    float a = 3.14;
    // int x = 5;  // Commenting this changes everything
    printf("%d\n", a);  // INCORRECT: using %d for a float
    return 0;
}

If you comment or uncomment the int x = 5; line, the printf output might change. Why does this happen?

This line alters how registers are set up and how the stack is arranged when the code is compiled. When the format specifier does not match the data type, printf reads a value from the wrong spot. And it reads whatever bits it finds as the wrong type.

A float like 3.14 will likely go into an XMM register. But %d tells printf to get from an integer register like %rdi. This means we are reading from the wrong register completely, which gives us garbage.

Why Does This Happen?

The C language does not keep track of types for variadic arguments. How printf() calls work right depends completely on your format string correctly stating what types come after it. If you lie—or even make a small mistake—the result is undefined behavior in C.


What Is Undefined Behavior in C?

The Danger Zone

Undefined behavior (UB) in C is any operation that the C standard does not define. Once it happens, no one can promise how your program will act. It may:

  • Crash the program
  • Output garbage
  • Work one moment and fail the next
  • Behave differently on each compile

Examples of undefined behavior include:

  • Accessing uninitialized memory
  • Dividing by zero
  • Reading out-of-bounds arrays
  • Mismatching format specifiers in variadic functions

The C Standard (ISO/IEC 9899:2018, § J.2) clearly calls these UB. And compilers do not have to show any warning or error for them.


Format Specifier Mismatch = Disaster Waiting to Happen

A Real Mismatch Example

Here is when things go wrong:

float num = 3.14;
printf("%d\n", num);  // WRONG: expects int, gets float

Why is this incorrect?

  • %d tells printf to expect an int. This type is kept in general-purpose registers.
  • But num is a float. This type is sent in floating-point registers (on most systems).
  • printf() does not know what type it is looking at. It just gets data from where it expects the next argument to be.

So it reads bits from the wrong place. And it reads them in the wrong way. This leads to strange, uneven, and system-specific results.

What is more confusing, adding or removing variables like int x = 3; may change where registers are assigned. And this can lead to a different result even if you do not change the print statement.


Compiler Internals: Register Behavior on x86_64

To understand this problem well, think about how arguments are passed at the machine level.

According to the System V AMD64 ABI:

  • The first six integer or pointer arguments are passed in these general-purpose registers:
    • %rdi, %rsi, %rdx, %rcx, %r8, %r9
  • The first eight floating-point arguments (float or double) are passed in:
    • %xmm0 to %xmm7

When printf() expects an int (%d) but you actually send a float, it will read from the int register (e.g., %rdi). But the value is instead in %xmm0.

If you uncomment a line, even one that does not create output, it can change which values go into which registers. Then UB shows up, giving new (but still wrong) outputs.


Type Promotions and Variadic Argument Semantics

In a variadic function like printf, type promotions apply:

  • float is automatically promoted to double
  • char and short promote to int

This means:

float f = 3.14f;
printf("%f\n", f);  // OK: float promoted to double

But:

printf("%d\n", 3.14);  // WRONG: double provided, int expected

This is NOT okay because 3.14 is a double, and %d expects an int. This causes undefined behavior.

printf() does not know the real types. It completely depends on what you say in the format string.


Optimization Flags Reveal Bugs

Compiling with optimization flags (-O1 to -O3) makes bugs harder to see.

Optimizations may:

  • Eliminate variables (like our int x = 5;)
  • Reorder function calls
  • Change register allocation

This can change how arguments are sent to printf(). And when there is UB caused by a format mismatch, this reordering may give different results.

A common case is this:

  • ✅ Works correctly with -O0 (no optimization)
  • ❌ Breaks with -O2 or -O3

Why? You rely on how the compiler works inside. It can change how code runs. This is because UB means nothing is certain.


Diagnosing and Debugging Undefined Behavior

Don't guess—use tools.

Use Compiler Warnings

Compile with:

gcc -Wall -Wextra -Wformat=2 -o program program.c

This will show warnings for most common format problems:

warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘float’

Use Sanitizers

Tools like UBSan (Undefined Behavior Sanitizer) and ASan (Address Sanitizer) work very well:

clang -fsanitize=undefined -g program.c -o program
./program

Use Valgrind

For memory errors:

valgrind ./program

Inspect Low-Level Behavior

Use objdump or gdb to look at how registers are used and how the stack is set up. This can show you exactly where values are being kept and gotten.


Safe printf Usage in C

Here is how to match the C format specifiers and avoid undefined behavior in printf:

  1. 🧾 Always match format specifier and data type.
  2. 🛑 Never cast values to “make a warning go away.” Fix the mismatch instead.
  3. 👀 Watch promotions: Know how char, float act in functions with a changing number of arguments.
  4. 🧪 Compile with warnings enabled: -Wall -Wextra -Wformat=2.
  5. 🔐 Prefer safer functions like snprintf() or fprintf() when possible.
  6. ❌ Avoid variadic functions for critical systems code.

Following strict format rules helps make sure your code works well and can run on different systems.


Real-World printf Mishaps

Dangerous Pointer Misuse

void debug_log(unsigned int ptr) {
    printf("ptr: %s\n", ptr);  // BAD: expected `char*`, got `unsigned int`
}

What's wrong?

  • %s expects a pointer to a string.
  • unsigned int is not a pointer.
  • This might print garbage, crash with segmentation fault, or seemingly "work" based on what ptr holds.

Unsigned Overflow and Sign Errors

unsigned char c = 255;
printf("%d\n", c);

This may print -1!

Why?

  • unsigned char becomes an int, maybe as -1. This happens based on if char is signed by default on your system.
  • Using %u would make it clear.

Writing Portable and Safe C Code

Portability means your code acts the same no matter the operating system, compiler, or computer type.

To make this happen:

  • Trust diagnostics: enable aggressive warnings.
  • Avoid UB entirely—don't attempt to detect or “fix” it by commenting/uncommenting lines.
  • Use compile-time checks (macros, static asserts) for format safety.
  • Use static analysis and automated linting tools early and throughout development.

Static Analysis and CI Toolchains

Modern toolchains give strong static analysis tools:

  • clang-tidy: Detects common C idioms and format mismatch errors.
  • cppcheck: Offers general-purpose static analysis.
  • Compiler warnings: -Wformat -Wformat=2 find dangerous problems with types.
  • Add these to CI tools like GitHub Actions, GitLab CI, or Jenkins to find new bugs early.

Static analysis helps you see more. This makes sure everyone uses the same format rules across all developers and machines.


Think of printf as a Teaching Tool

printf strange behaviors teach a lot. They do more than just break things:

  • You will learn about variadic functions and how arguments change types.
  • You will learn machine-level calling conventions (x86_64, ARM).
  • You will see how C works closely with computer parts.

Finding the problems from misused printf helps you become good at debugging, system programming, and knowing what optimizers do to your code.


Final Thoughts: Respect the Format

Small code changes, like commenting out a variable, should not change what your program prints. But in C, especially with functions like printf, they often do. The main reason is undefined behavior, and this usually comes from format specifiers that do not match.

To avoid the trap:

  • Rely on strict format matching.
  • Enable and respect warnings.
  • Use modern tools to catch bugs during compilation and testing.
  • Recognize that small mistakes in printf show bigger problems in how you write code.

With this knowledge, you will not only stop printf problems, but you will also write better, safer, and more professional C code.


References

  • International Organization for Standardization. (2018). ISO/IEC 9899:2018: Programming languages—C. Geneva: ISO.
  • GNU Compiler Collection. (n.d.). GCC Warning Options. https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
  • Intel Corporation. (2019). System V Application Binary Interface AMD64 Architecture Processor Supplement. https://refspecs.linuxbase.org
  • Meyers, S. (2014). Effective Modern C++. O'Reilly Media.
  • Syscall, X. (2023). Format string mismatches and UB. Discussed impact of incorrect format usage causing register misinterpretation on x86_64.
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading