printf(3)

C library function

Writes formatted text to standard output. The formats to know (%d, %ld, %zu, %p, %s), what printf returns and the -Wformat warnings.

Synopsis
#include <stdio.h>
int printf(const char *restrict format, ...);

Description

printf walks through its format one character at a time. Ordinary text is copied as is; each % starts a conversion, which consumes the next argument and writes it out as text. Since printf takes a variable number of arguments, it does not know their types: the format tells it, and the format has to match exactly.

FormatExpected argument
%d or %iint
%u, %xunsigned int, in decimal or in hexadecimal
%ld, %lldlong, long long
%zusize_t, the type of sizeof and of strlen
%fdouble, or a float, converted to double at the call
%ca character, passed as an int
%sa char * to a string terminated by '\0'
%pa void *
%%nothing, prints a %

The text does not go straight to the screen: it goes through the stdout buffer, which is flushed at every \n on a terminal and in blocks on a pipe. That is why printf prints nothing before a crash.

Return value

The number of bytes written, or a negative value on error. Bytes, not characters: in a UTF-8 source file, printf("café\n") returns 6, because the “é” takes two bytes.

Pitfalls

  • A format that does not match the type is undefined behavior. -Wall turns on -Wformat, which catches it at compile time. For a sizeof, GCC suggests %ld because size_t is an unsigned long on this machine, but %zu is the only portable way to write it:

Terminal
$ gcc -Wall -Wextra -g warn.c -o warnwarn.c: In function 'main':warn.c:7:14: warning: format '%d' expects argument of type 'int', but argument 2 has type 'long unsigned int' [-Wformat=]    7 |     printf("%d\n", sizeof scores);      |             ~^     ~~~~~~~~~~~~~      |              |     |      |              int   long unsigned int      |             %ld
  • printf(text), when text comes from the user, interprets every % it contains, and %n even lets it write to memory. Write printf("%s", text).

  • %p expects a void *: convert the pointer, (void *)p.

  • The fix-the-format exercise has you fix a wrong format under -Werror, with a size_t and a long.

Example

formats.c
#include <stdio.h>
int main(void){    int scores[5] = {12, 15, 9, 18, 11};    long total = 6000000000L;    int n;
    n = printf("Yasmine is %d years old\n", 19);    printf("%d bytes\n", n);    n = printf("café\n");    printf("%d bytes\n", n);    printf("%zu bytes at address %p\n", sizeof scores, (void *)scores);    printf("total: %ld\n", total);    return 0;}
Terminal
$ gcc -Wall -Wextra -g formats.c -o formats$ ./formatsYasmine is 19 years old24 bytescafé6 bytes20 bytes at address 0xffffefc7e410total: 6000000000

The \n counts toward the 24 bytes of the first line. The address changes on every run.

C

mini_printf

Write mini_printf, which expands %c, %s, %d, %x and %% with write and returns the exact number of bytes written.

Open the exercise

Now write it yourself.