What are C printf Format Specifiers Explained-Embedded C Programming Training in Hyderabad

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Embedded C Course · Chapter 3 · Lecture 2

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.

7
Specifiers covered
5
Runnable programs
4
Predict-the-output tasks

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.

printf format specifiers control string scanf whitespace embedded C

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, %x and %% correctly.
  • Predict the exact text printf will produce, including the surprising 3.300000 for 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.h provides).
  • Knowledge of the basic data types: int, float, char, and strings as character arrays.
  • A Linux machine (or WSL) with gcc installed. Check with gcc --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.

printf matching specifiers to arguments
Control string is scanned left to right
Plain text → copied to output
%d → 1st extra argument, shown as an integer
%f → 2nd extra argument, shown as a decimal number
%c → 3rd extra argument, shown as one character
End of string → output flushed to stdout, count returned

Two rules follow directly from this picture, and beginners trip on both:

  1. 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 &.)
  2. 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.

SpecifierExpectsPrintsExample result
%dintSigned decimal integer36
%ffloat or doubleDecimal number, 6 digits after the point by default3.300000
%ccharOne characterB
%schar * (null-terminated)A whole stringSTM32
%ounsigned intOctal (base 8)264
%x / %Xunsigned intHexadecimal, lowercase / uppercaseb4 / B4
%%nothingA 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
Which argument feeds which specifier
%s ← board (const char *) prints STM32
%d ← temp_c (int) prints 36
%f ← vref (float) prints 3.300000
%c ← grade (char) prints B

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.

MistakeWhat happensFix
Fewer arguments than specifiersprintf reads garbage or crashesCount specifiers and arguments
%d with a floatMeaningless number printedUse %f
%s with a single charLikely crashUse %c
Using & with printf variablesPrints an address, not the valuePass the plain variable
Forgetting \nOutput is stuck on one line or delayedEnd log lines with a newline
Unescaped percent sign in textprintf treats it as a specifierWrite %%

Best Practices, Performance and Security

Best Practices

  • Compile with -Wall -Wextra every time. The compiler checks your format strings for free.
  • Match each of your C printf format specifiers to the exact type. Use %u for unsigned int, %zu for size_t, and %ld for long.
  • Prefer %.2f style 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: printk and pr_info use 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

  1. What does printf return? The number of characters written, or a negative value if an output error occurred.
  2. Why is printf(str) dangerous? If str contains specifiers it becomes a format-string vulnerability. Use printf("%s", str).
  3. How do you print a literal percent sign? Write %%.
  4. Why can one %f print both float and double? Arguments of type float are promoted to double in a variable argument list.
  5. What is the difference between %x and %X? Lowercase versus uppercase hexadecimal digits.
  6. 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.
  • %f shows six decimals by default, %x is 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 -Wformat warnings, 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

Leave a Reply

Your email address will not be published. Required fields are marked *