Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
C printf Format Specifiers Explained
Learn how printf turns raw variables into readable text, with five runnable programs, predict-the-output challenges, and a quiz. Part of our free embedded systems course.
Every embedded engineer, kernel hacker and student ends up doing the same thing sooner or later: printing a value to see what the program is really doing. Whether it is a UART log on a microcontroller, a printk line in dmesg, or a plain terminal program, the tool is the same family of format strings. In this lecture of our free embedded systems course you will master C printf format specifiers: what they are, how printf matches them to your variables, and what silently goes wrong when you get them wrong.
We also take a short detour to the input side and see how scanf treats spaces in its control string, because the same “format string” idea runs in both directions. If you are following our free embedded linux course or the free linux kernel development course, this is groundwork you will use in every driver you write.
What You Will Learn
By the end of this lecture you will be able to:
- Explain what a control string is and how printf walks through it from left to right.
- Use
%d,%f,%c,%s,%o,%xand%%correctly. - Predict the exact text printf will produce, including the surprising
3.300000for a float. - Use the return value of printf, which most beginners never look at.
- Understand why
scanf("%d %d %d")accepts numbers separated by any amount of whitespace. - Spot and fix format mismatches using compiler warnings.
Prerequisites
- Lecture 1 of this chapter (what standard input and output are, and what
stdio.hprovides). - Knowledge of the basic data types:
int,float,char, and strings as character arrays. - A Linux machine (or WSL) with
gccinstalled. Check withgcc --version.
How printf Format Specifiers Work
The prototype of printf in the C standard library looks like this:
int printf(const char *format, ...);
The first argument is the control string (also called the format string). It is made of two kinds of things: ordinary text that is copied to the output untouched, and conversion specifications that begin with a percent sign. Each conversion specification is a placeholder that says “take the next argument, interpret it as this type, and print it here”.
Think of the control string as a form letter and the extra arguments as the data you fill in. printf reads the form from left to right. Plain text goes straight out. When it meets a %, it pulls the next unused argument from your list, converts it according to the letter that follows, and continues.
Two rules follow directly from this picture, and beginners trip on both:
- No ampersand. printf only needs to read your values, so you pass the variable itself, never its address. (scanf is the opposite: it must write into your variable, so it needs
&.) - If the control string has no conversion specifications, you pass no extra arguments. A call like
printf("Booting...\n")is just text.
printf Returns a Value
printf is a function, so it returns something: the number of characters it printed (or a negative number on error). Most programs ignore it, but it is handy for aligning output and a great way to prove to yourself exactly what was sent out. Save this as ep_hello_printf.c:
#include <stdio.h>
int main(void)
{
int count = printf("Embedded C is fun\n");
printf("printf returned %d\n", count);
return 0;
}
Build and run it:
gcc -Wall -Wextra -std=c11 -o ep_hello_printf ep_hello_printf.c
./ep_hello_printf
Expected output:
Embedded C is fun
printf returned 18
The number is 18 because the sentence has 17 visible characters and the \n at the end counts as the 18th character. Nothing is hidden: what you count is what you get.
Modern C note: older books write main() with no return type and no return statement. Current C standards do not allow the implicit int, and compilers with -Wall will complain. Always write int main(void) and end with return 0;.
The Everyday C printf Format Specifiers
Here is the table of C printf format specifiers you will keep coming back to. Each row shows the letter, the type it expects, and a sample result.
| Specifier | Expects | Prints | Example result |
|---|---|---|---|
%d | int | Signed decimal integer | 36 |
%f | float or double | Decimal number, 6 digits after the point by default | 3.300000 |
%c | char | One character | B |
%s | char * (null-terminated) | A whole string | STM32 |
%o | unsigned int | Octal (base 8) | 264 |
%x / %X | unsigned int | Hexadecimal, lowercase / uppercase | b4 / B4 |
%% | nothing | A literal percent sign | % |
Mixing Text and Specifiers in One Call
You will almost always put several specifiers into a single control string, so one line of output can describe a whole device. Save this as ep_board_status.c:
#include <stdio.h>
int main(void)
{
int temp_c = 36;
float vref = 3.3f;
char grade = 'B';
const char *board = "STM32";
printf("Board=%s Temp=%d C Vref=%f Grade=%c\n", board, temp_c, vref, grade);
printf("Vref with 2 decimals = %.2f\n", vref);
return 0;
}
gcc -Wall -Wextra -std=c11 -o ep_board_status ep_board_status.c
./ep_board_status
Expected output:
Board=STM32 Temp=36 C Vref=3.300000 Grade=B
Vref with 2 decimals = 3.30
Why six zeros after the point? %f prints six digits after the decimal point unless you tell it otherwise. That is not rounding noise in your variable; it is simply the default precision. Writing %.2f asks for exactly two digits, and %.0f asks for none. One more detail worth knowing: when a float is passed through printf’s variable argument list it is silently promoted to double, which is why %f works for both types.
Octal, Hexadecimal and the Percent Sign
Embedded work is full of register values, so base conversion in printf is not a toy feature. Save this as ep_bases.c:
#include <stdio.h>
int main(void)
{
int n = 180;
printf("dec=%d oct=%o hex=%x HEX=%X\n", n, n, n, n);
printf("with prefix: %#o %#x %#X\n", n, n, n);
printf("Battery at 85%%\n");
return 0;
}
gcc -Wall -Wextra -std=c11 -o ep_bases ep_bases.c
./ep_bases
Expected output:
dec=180 oct=264 hex=b4 HEX=B4
with prefix: 0264 0xb4 0XB4
Battery at 85%
Notice that we passed the same variable n four times. Each specifier consumes its own argument, so the value has to be repeated. Also notice that %x prints lowercase b4. Some older textbooks show uppercase output for %x; real implementations print lowercase for %x and uppercase only for %X. The # flag adds the familiar 0 and 0x prefixes. And since % is special, a real percent sign must be written as %%.
Tip: when you debug registers, print them as 0x%08x to get zero-padded, fixed-width hexadecimal such as 0x000000b4. Fixed width makes bit patterns easy to compare by eye.
Predict the Output (Try Before You Click)
Reading is not learning until you commit to an answer. For each snippet, write down your prediction first, then open the answer.
Challenge 1
int a = 12;
printf("a=%d, double=%d\n", a, a * 2);
Show answer
a=12, double=24
Arguments can be full expressions. printf evaluates a * 2 before the call.
Challenge 2
float f = 2.5f;
printf("%f | %.1f | %.0f\n", f, f, f);
Show answer
2.500000 | 2.5 | 2
The last value is 2 because %.0f rounds to the nearest even number when the value is exactly halfway under the default rounding mode.
Challenge 3
printf("%c%c%c\n", 'A', 'B' + 1, 67);
Show answer
ACC
A character is just a small integer. 'B' + 1 is 67 and so is the literal 67, and %c prints the character whose code is 67, which is C.
Challenge 4
printf("%d%% of %s\n", 50, "flash");
Show answer
50% of flash
The %% consumes no argument, so 50 goes to %d and "flash" goes to %s.
Reading Side: Spaces in the scanf Control String
The format string idea works in reverse for scanf. Here the specifiers describe the input you expect. The question beginners ask is: what do the spaces between the specifiers mean? Try this program, ep_scan_space.c:
#include <stdio.h>
int main(void)
{
int a, b, c;
int n = scanf("%d %d %d", &a, &b, &c);
printf("converted=%d a=%d b=%d c=%d\n", n, a, b, c);
return 0;
}
gcc -Wall -Wextra -std=c11 -o ep_scan_space ep_scan_space.c
printf '12 34\n 56\n' | ./ep_scan_space
printf '1 2 3' | ./ep_scan_space
Expected output:
converted=3 a=12 b=34 c=56
converted=3 a=1 b=2 c=3
Both inputs worked even though the first has extra spaces and a line break in the middle. A space in the control string matches any amount of whitespace, including none, and the numeric specifiers already skip leading whitespace by themselves. So "%d %d %d" and "%d%d%d" behave the same for numbers. The scanf return value is the number of items successfully converted, and checking it is the professional way to detect bad input.
Anything in the control string that is not whitespace or a specifier must match the input exactly. That is useful for structured data. Save this as ep_ip_parse.c:
#include <stdio.h>
int main(void)
{
int o1 = 0, o2 = 0, o3 = 0, o4 = 0;
int n = scanf("%d.%d.%d.%d", &o1, &o2, &o3, &o4);
printf("fields read = %d\n", n);
printf("octets: %d | %d | %d | %d\n", o1, o2, o3, o4);
return 0;
}
gcc -Wall -Wextra -std=c11 -o ep_ip_parse ep_ip_parse.c
printf '192.168.1.10\n' | ./ep_ip_parse
printf '192-168-1-10\n' | ./ep_ip_parse
Expected output:
fields read = 4
octets: 192 | 168 | 1 | 10
fields read = 1
octets: 192 | 0 | 0 | 0
With dashes instead of dots, scanf read 192, then hit a character that did not match the literal dot, and stopped. It reported one successful field. That return value is your early warning that the input was malformed.
Common printf Format Specifier Mistakes
Mismatched C printf format specifiers are the number one printf bug. They compile, they sometimes even run, and they print garbage. Save this deliberately broken file as ep_mismatch.c and compile it:
#include <stdio.h>
int main(void)
{
int level = 7;
printf("Level = %s\n", level);
printf("Level and mode = %d %d\n", level);
return 0;
}
gcc -Wall -Wextra -std=c11 -o ep_mismatch ep_mismatch.c
The compiler output:
ep_mismatch.c:6:22: warning: format '%s' expects argument of type 'char *', but argument 2 has type 'int' [-Wformat=]
ep_mismatch.c:7:34: warning: format '%d' expects a matching 'int' argument [-Wformat=]
Do not run this program. Passing an integer where %s expects an address makes printf dereference the number 7 as if it were a pointer, and a missing argument makes it read whatever happens to be next. Both are undefined behaviour in C. The lesson: never ignore -Wformat warnings.
| Mistake | What happens | Fix |
|---|---|---|
| Fewer arguments than specifiers | printf reads garbage or crashes | Count specifiers and arguments |
%d with a float | Meaningless number printed | Use %f |
%s with a single char | Likely crash | Use %c |
Using & with printf variables | Prints an address, not the value | Pass the plain variable |
Forgetting \n | Output is stuck on one line or delayed | End log lines with a newline |
| Unescaped percent sign in text | printf treats it as a specifier | Write %% |
Best Practices, Performance and Security
Best Practices
- Compile with
-Wall -Wextraevery time. The compiler checks your format strings for free. - Match each of your C printf format specifiers to the exact type. Use
%uforunsigned int,%zuforsize_t, and%ldforlong. - Prefer
%.2fstyle precision over default six digits when you are showing sensor values to humans. - Print one meaningful thing per line during debugging, and end every line with
\n.
Performance Considerations
printf is convenient but it is not free. It parses the format string at run time and may take locks on the output stream. On a small microcontroller, a single printf with floating-point support can add several kilobytes of code, and with the newlib-nano library floating-point printing is disabled by default to save space. Inside tight loops or interrupt handlers, avoid printf entirely and record raw values to a buffer instead.
Security Considerations
Never pass user-controlled text as the control string. A call like printf(user_text) lets an attacker include specifiers such as %x or %n and read or even modify memory. This is the classic format-string vulnerability. The safe form is always printf("%s", user_text).
Where This Shows Up in Real Embedded and Kernel Work
- UART debug logs: firmware engineers retarget printf to a serial port and read values such as ADC readings with the same specifiers you learned here.
- Linux kernel:
printkandpr_infouse nearly identical specifiers, which is why this lecture belongs in a free linux device drivers course. The kernel does not support%f, because floating-point math is avoided in kernel code. - Log parsing: scanf-style patterns, like our dotted address example, are the same idea behind many quick tools that read configuration text.
Quick Quiz
Question 1
What does printf("%x", 255) print?
Show answer
ff, in lowercase. Use %X for FF.
Question 2
Why does printf("%f", 2.0) print 2.000000?
Show answer
The default precision of %f is six digits after the decimal point.
Question 3
A program calls scanf("%d%d", &x, &y) and the user types 7 abc. What does scanf return?
Show answer
1. Only x was converted. The second conversion failed on the letter a.
Interview Questions on printf
- What does printf return? The number of characters written, or a negative value if an output error occurred.
- Why is
printf(str)dangerous? Ifstrcontains specifiers it becomes a format-string vulnerability. Useprintf("%s", str). - How do you print a literal percent sign? Write
%%. - Why can one
%fprint both float and double? Arguments of type float are promoted to double in a variable argument list. - What is the difference between
%xand%X? Lowercase versus uppercase hexadecimal digits. - What does a space in a scanf format string mean? It matches zero or more whitespace characters.
Summary and Key Takeaways
- C printf format specifiers are the conversion specifications, starting with
%, that you mix with plain text in the control string. - printf matches specifiers to arguments strictly in order, left to right.
- No
&for printf; yes for scanf. %fshows six decimals by default,%xis lowercase,%%prints a percent sign.- printf returns the character count, and scanf returns the number of converted items.
- Whitespace in a scanf format matches any amount of whitespace; other characters must match exactly.
- Heed
-Wformatwarnings, and never use untrusted text as a format string.
Conclusion
You now understand the C printf format specifiers well enough to log, debug and present data confidently, which is exactly the skill every learner in a free embedded systems course needs before touching hardware. The next lecture in this free linux development course covers escape sequences such as the newline and tab, which control how the text you format is laid out on screen or on a serial terminal. Practice by modifying the programs above: change types, remove arguments on purpose, and watch the compiler warnings. Breaking things safely is the fastest way to learn.
Frequently Asked Questions
What are format specifiers in C?
They are placeholders beginning with % inside a printf or scanf control string that describe the type of value to print or read, such as %d for int and %s for strings.
Why does printf print 3.300000 for a float set to 3.3?
Because %f prints six digits after the decimal point by default. Use %.1f or %.2f to control it.
Do I need the ampersand in printf like I do in scanf?
No. printf only reads values. scanf must store values, so it needs the address of each variable.
What happens if I give printf too few arguments?
The behaviour is undefined. It may print garbage or crash. Compile with -Wall to catch this.
Is printf available in embedded systems and the Linux kernel?
Microcontroller toolchains usually provide a reduced printf you can redirect to a UART. The Linux kernel provides printk, which uses very similar specifiers but does not support floating point.
Is this course really free?
Yes. EmbeddedPathashala offers this free embedded systems course, free embedded linux course, free linux device drivers course and free linux kernel development course for students.
Keep Going With the Free Embedded C Course
Next up: escape sequences, the small backslash codes that control newlines, tabs and carriage returns in your output.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments