CFormatted I/O in Depth

Formatted I/O in Depth

You've already met the basic format specifiers like %d and %s. printf supports a much richer mini-language for controlling width, precision, and justification, and its sibling functions sprintf/snprintf let you format text into a string instead of printing it directly. This page covers both in depth.

Width

A number placed right after the % sets a minimum field width. If the value is shorter, it's padded with spaces (right-justified by default):

C
#include <stdio.h>

int main(void) {
    printf("[%5d]\n", 42);   /* pads with spaces to width 5 */
    printf("[%5d]\n", 12345); /* value already wider than 5 -- not truncated */
    return 0;
}
[   42]
[12345]
Left-justifying with -

A - flag right after the % left-justifies within the field width instead — very useful for aligning columns in a report:

C
printf("[%-10s]\n", "Ada");   /* left-justified, padded to width 10 */
printf("[%10s]\n", "Ada");    /* right-justified (default) */
[Ada       ]
[       Ada]
Precision

A . followed by a number sets precision, whose meaning depends on the conversion: for %f it's the number of digits after the decimal point; for %s it's the maximum number of characters printed:

C
printf("%.2f\n", 3.14159);      /* 2 digits after the decimal point */
printf("%.5s\n", "Hello, World!"); /* only the first 5 characters */
3.14
Hello
Combining width and precision

Width and precision can be combined in a single specifier — %width.precisionconversion, e.g. %8.2f:

C
#include <stdio.h>

int main(void) {
    double prices[] = { 3.5, 129.995, 0.4 };
    for (int i = 0; i < 3; i++) {
        printf("[%8.2f]\n", prices[i]);
    }
    return 0;
}
[    3.50]
[  130.00]
[    0.40]
sprintf and snprintf — formatting into a string

sprintf works exactly like printf, except the result is written into a char buffer you provide instead of going to the screen. snprintf does the same thing but also takes a maximum buffer size, so it can never write past the end of your buffer:

C
#include <stdio.h>

int main(void) {
    char message[64];

    sprintf(message, "Score: %d/%d", 85, 100); /* no bounds checking! */
    printf("%s\n", message);

    char safe_message[16];
    snprintf(safe_message, sizeof(safe_message), "Score: %d/%d", 85, 100);
    printf("%s\n", safe_message); /* truncated safely if it doesn't fit */
    return 0;
}
Prefer snprintf over sprintf
`sprintf` has exactly the same fundamental flaw as `gets`: it has no idea how big your destination buffer is, so a long enough result will overflow it. `snprintf` takes the buffer's size as a parameter and guarantees it will never write more than that many bytes (including the null terminator), truncating the output if necessary. There is essentially never a good reason to reach for `sprintf` over `snprintf` in new code.
Worked example: building a report line

C
#include <stdio.h>

int main(void) {
    const char *name = "Widget";
    int quantity = 12;
    double unit_price = 4.999;
    char line[64];

    snprintf(line, sizeof(line), "%-10s x%-4d $%7.2f",
              name, quantity, unit_price);
    printf("%s\n", line);
    return 0;
}
Widget x12 $ 5.00
The return value of snprintf
`snprintf` returns the number of characters that *would* have been written if the buffer were large enough, not counting the null terminator. Comparing that return value against your buffer size is a reliable way to detect whether truncation actually occurred.