C Format Specifiers

C format specifiers are placeholders such as %d, %s, and %.2f inside a format string. They tell input and output functions how to interpret the values that follow the string. Format specifiers matter because C does not automatically know the type of each extra argument passed to functions like printf, so the format string becomes a contract between your code and the library function.

Overview: How Format Specifiers Work

The most common formatted I/O functions are printf for output and scanf for input. Both use a format string: ordinary characters are copied or matched literally, while conversion specifications begin with %. In printf("Age: %d", age), the text Age: is printed as-is, and %d says that the next argument should be treated as a signed int.

This is powerful, but it is also less protected than many beginner-friendly languages. A function such as printf receives a variable number of arguments. At run time, it walks through the format string and fetches the next argument whenever it sees a specifier. If the specifier says %f, printf expects a floating-point argument of the promoted type double. If the actual argument is an int, the bytes are interpreted using the wrong rule, which causes undefined behavior. The compiler can often warn you, but the C language still expects you to match specifiers and argument types exactly.

Format specifiers can do more than choose a type. They can set a minimum field width, choose left or right alignment, pad with zeros, limit string length, and control the number of digits printed after a decimal point. For input, they can also limit how many characters are read into a character array, which is essential for avoiding buffer overflow.

Syntax

A general printf conversion specification has this shape:

%[flags][width][.precision][length]specifier
Part Meaning Example
% Starts a conversion specification. Use %% to print a literal percent sign. %%
flags Optional modifiers such as left alignment or zero padding. %-8d, %08d
width Minimum number of character positions to use. %10s
.precision For floating output, digits after the decimal point; for strings, maximum characters printed. %.2f, %.5s
length Adjusts the expected argument type for larger, smaller, or special integer types. %ld, %lld, %zu
specifier The final conversion letter that selects the kind of data. d, f, s

Common Specifiers

Specifier Use with printf Typical argument type
%d or %i Signed decimal integer int
%u Unsigned decimal integer unsigned int
%c Single character int after promotion, often from char
%s Null-terminated string char *
%f Floating-point decimal notation double
%e Scientific notation double
%g Shorter of decimal or scientific notation double
%p Pointer address void *
%zu Unsigned size value size_t

For scanf, the letters look similar, but the arguments are usually addresses because the function must store data into your variables. For example, use %d with &number for an int, and use %lf with &value for a double. A character array used with %s already behaves like a pointer to its first element, so you pass the array name, not &array.

Examples

Printing Basic Types

#include <stdio.h>
#include <stddef.h>

int main(void)
{
    char name[] = "Mina";
    char grade = 'A';
    int score = -12;
    unsigned int lives = 3;
    size_t name_length = sizeof name - 1;

    printf("Player: %s\n", name);
    printf("Grade: %c\n", grade);
    printf("Score change: %d\n", score);
    printf("Lives left: %u\n", lives);
    printf("Name length: %zu\n", name_length);

    return 0;
}

Output:

Player: Mina
Grade: A
Score change: -12
Lives left: 3
Name length: 4

Each specifier matches one argument after the format string. The array name is printed with %s, the character with %c, the signed integer with %d, the unsigned integer with %u, and the size_t value with %zu.

Controlling Width, Alignment, And Precision

#include <stdio.h>

int main(void)
{
    int count = 42;
    double price = 3.5;
    const char word[] = "format";

    printf("|%8d|\n", count);
    printf("|%-8d|\n", count);
    printf("|%08d|\n", count);
    printf("|%10.2f|\n", price);
    printf("|%.5s|\n", word);

    return 0;
}

Output:

|      42|
|42      |
|00000042|
|      3.50|
|forma|

The width in %8d creates a field at least eight characters wide. The minus flag in %-8d left-aligns the value, while %08d pads with zeros. For %10.2f, the full field is ten characters wide and the number has exactly two digits after the decimal point. For %.5s, precision limits the string to five printed characters.

Reading With scanf-Style Specifiers

#include <stdio.h>

int main(void)
{
    const char line[] = "Ava 19 3.75";
    char name[20];
    int age;
    double gpa;

    int fields = sscanf(line, "%19s %d %lf", name, &age, &gpa);

    printf("Fields read: %d\n", fields);
    printf("Name: %s\n", name);
    printf("Age: %d\n", age);
    printf("GPA: %.2f\n", gpa);

    return 0;
}

Output:

Fields read: 3
Name: Ava
Age: 19
GPA: 3.75

sscanf works like scanf, except it reads from a string instead of the keyboard. The width in %19s prevents more than 19 non-whitespace characters from being stored in name, leaving room for the terminating null character. The int and double variables are passed by address so the function can write into them.

Using Length Modifiers

#include <stdio.h>
#include <stddef.h>

int main(void)
{
    long population = 331002651L;
    long long meters_in_light_year = 9460730472580800LL;
    size_t buffer_size = 64;

    printf("Population: %ld\n", population);
    printf("Meters in a light-year: %lld\n", meters_in_light_year);
    printf("Buffer size: %zu bytes\n", buffer_size);

    return 0;
}

Output:

Population: 331002651
Meters in a light-year: 9460730472580800
Buffer size: 64 bytes

The final conversion letter still describes the kind of value, but the length modifier changes the exact type expected. %ld expects a long, %lld expects a long long, and %zu expects the unsigned integer type used by size_t.

How It Works Step By Step

  1. The function receives the format string as its first argument.
  2. It scans the string from left to right, copying normal characters to the output or matching them from the input.
  3. When it finds %, it reads any flags, width, precision, and length modifier until it reaches the conversion specifier.
  4. For output, it fetches the next argument and converts that value into text according to the specifier.
  5. For input, it reads characters, converts them into the requested type, and stores the result through the pointer you supplied.
  6. The process repeats until the end of the format string or, for input, until a conversion fails.

Because this mechanism depends on the order and types of the extra arguments, format strings are more than decoration. They are instructions that control how raw argument values are consumed.

Common Mistakes

Using The Wrong Type

A common error is using %d for a double or %f for an int. That is not a harmless display problem; it asks the function to read the argument using the wrong type. Use printf("%.2f", total) for a double and printf("%d", count) for an int.

Forgetting Addresses With scanf

scanf("%d", number) is wrong when number is an int, because scanf needs a place to store the result. Write scanf("%d", &number). For a character array such as char name[20];, write scanf("%19s", name), because the array name already provides the address of its first element.

Confusing printf And scanf For double

With printf, both float and double floating arguments are printed with %f because float is promoted to double in a variable argument list. With scanf, use %f for a float * and %lf for a double *.

Unbounded String Input

scanf("%s", name) can overflow the array if the user types too many characters. Put a maximum width one less than the array size: for char name[20];, use %19s.

Best Practices

  • Match every format specifier to the exact argument type.
  • Compile with warnings enabled, such as -Wall -Wextra, so the compiler can catch many format mismatches.
  • Use precision for money-like display, such as %.2f, but remember that binary floating-point is not exact arithmetic.
  • Use width and alignment to create readable columns instead of manually counting spaces.
  • Always limit %s input with a field width.
  • Check the return value of scanf, sscanf, and related functions to confirm how many fields were successfully read.
  • Use the proper length modifier for long, long long, and size_t.
  • Use %% when you need to print a percent sign.

Practice Exercises

  1. Write a program that stores a product name, quantity, and price, then prints them in aligned columns with the price shown to two decimal places.
  2. Write a program that reads a line like Ravi 24 88.5 with sscanf, stores the fields in variables, and prints a sentence using the values.
  3. Write a program that prints the same integer right-aligned, left-aligned, and zero-padded in a field of width 6.

Summary

  • Format specifiers tell C formatted I/O functions how to print or read values.
  • The specifier must match the argument type; mismatches can cause undefined behavior.
  • Width, precision, and flags let you control alignment, padding, decimal places, and string length.
  • scanf-style functions usually need addresses, and %s input should always have a field width.
  • Length modifiers such as l, ll, and z are required for types beyond plain int.