CFormat Specifiers

Format Specifiers

printf and scanf (and their many relatives like fprintf, sprintf, and sscanf) don't know the types of their arguments the way a normal C function does — they figure out how to interpret each argument from a format string containing %-prefixed placeholders called format specifiers. Getting these right is essential: the compiler generally can't check them for you.

Common format specifiers

Specifier

Type

Meaning

%d / %i

int

Signed decimal integer

%u

unsigned int

Unsigned decimal integer

%f

double (printf) / float* (scanf)

Decimal floating-point

%lf

double

Same as %f for printf; required for double * with scanf

%c

char (widened to int)

A single character

%s

char *

A null-terminated string

%x / %X

unsigned int

Hexadecimal (lower/upper case letters)

%o

unsigned int

Octal

%p

void *

A pointer value, implementation-defined format

%%

(none)

A literal percent sign

C
#include <stdio.h>

int main(void) {
    int age = 30;
    unsigned int count = 5u;
    double price = 19.99;
    char grade = 'A';
    char *name = "Ada";
    int hex_value = 255;

    printf("Age: %d\n", age);
    printf("Count: %u\n", count);
    printf("Price: %f\n", price);
    printf("Grade: %c\n", grade);
    printf("Name: %s\n", name);
    printf("Hex: %x, Octal: %o\n", hex_value, hex_value);
    printf("Pointer: %p\n", (void *)name);
    printf("100%% done\n");
    return 0;
}
printf vs scanf for floating-point

A quirk that surprises a lot of beginners: printf("%f", someDouble) and printf("%lf", someDouble) behave identically, because when a float is passed to a variadic function like printf, it's automatically promoted to double — so %f already expects a double. But scanf receives a pointer, and pointers aren't promoted, so scanf truly does distinguish %f (expects float *) from %lf (expects double *). Mixing these up with scanf is a classic, hard-to-spot bug.

C
float f;
double d;

scanf("%f", &f);   /* correct: %f for a float* */
scanf("%lf", &d);  /* correct: %lf for a double* */
/* scanf("%f", &d);  WRONG -- undefined behavior, writes only 4 bytes into an 8-byte double */
Length modifiers

Length modifiers are prefixes placed before the conversion letter to target a different-sized integer type:

Modifier

Example

Targets

h

%hd

short int

hh

%hhd

signed char

l

%ld

long int

ll

%lld

long long int

z

%zu

size_t (the type returned by sizeof)

j

%jd

intmax_t

t

%td

ptrdiff_t

C
#include <stdio.h>

int main(void) {
    long long population = 8000000000LL;
    size_t array_size = sizeof(population);

    printf("Population: %lld\n", population);
    printf("Size in bytes: %zu\n", array_size);
    return 0;
}
Mismatched format specifiers are undefined behavior
The compiler does not check, at the language level, that the type of each argument matches its format specifier — `printf` and `scanf` simply trust the format string and read arguments from the call stack (or a register) accordingly. Passing an `int` where `%s` expects a `char *`, or a `double` where `%d` expects an `int`, is **undefined behavior**: it might print garbage, might crash, or might happen to work today and fail tomorrow after an unrelated code change. Always match the specifier to the argument's actual type.
Compiler help
GCC and Clang can catch many of these mistakes for you at compile time with `-Wall -Wformat` (often already implied by `-Wall`), which checks `printf`/`scanf`-family calls against the format string. It's good practice to always compile with `-Wall` enabled.