Code Capsules

Text Processing II: printf

Chuck Allison


The quintessential "first program" for the student of C is some variant of "Hello, world" such as

/* hello.c:   A first program */

#include <stdio.h>

main()
{
   printf("Hello, world\n");
}
This provides a starting point for discussing basic syntax and program layout, the notion of include files, and most importantly, how to produce output. Nothing is as encouraging or enabling as seeing the results of a program that works. Most programmers spend their first hours of C exploration discovering the ins and outs of printf. But mastering this most flexible of functions requires more than just a few hours. This capsule suggests some techniques to exploit the power of the printf family of functions.

Precision

Consider the output from the following statements:

printf("|%5.4d| |%-5.3s| |%5.0f|\n", 123, "a string", 45.6);
printf("|%+5d| |%+5d| |% 5d| |% 5d|\n", 123, -123, 123, -123);
printf("|%#5o| |%#5x| |%#5.0f|\n", 15, 15, 15.0);

Output:

 | 0123| |a s  | |  46|
 | +123| | -123| | 123| | -123|
 |  017| |  0xf| | 15.|
The first statement illustrates the effect of precision (specified with a decimal point followed by an integer). For an integer, it specifies the minimum number of digits to be displayed. This means that leading zeroes will appear, if necessary, to fill out the given precision. With strings, the precision specifies the maximum number of characters of the string to display (note that this example left-justifies the result because of the hyphen (-) flag). A precision of 0 causes a floating-point number to be rounded to the nearest integer and displayed without a decimal point. To display a decimal point but no subsequent decimal digits, use the # flag, as shown in the third statement. The + flag causes a sign to always be displayed, whereas the space flag (e.g., |% 5d|) substitutes a space for the plus sign. The # flag prints a number with its characteristic notation (namely, a leading 0 for octal, a leading 0x for hexadecimal, and a decimal point for real numbers). Under all circumstances, all significant digits are displayed, even if the field width (and precision in the case of integers) is exceeded.

Saving a Formatted String

It is often convenient to save a formatted string for future or repeated use, instead of sending it to an output device immediately. Traditionally called in-core formatting, you save a formatted string with the sprintf function. The statement

char s[9];
sprintf(s,"%2d/%02d/%02d",mon,day,year);
builds a date string in a character array.

sprintf always appends a terminating zero byte to the formatted string. It returns the number of characters formatted (not including the terminating null). The program in Listing 1 pastes input strings together into a single string. Each call to sprintf concatenates the current word to the end of the string by overwriting the zero byte at the end of the string with the first character of the word.

While this could be done just as well using strcpy followed by strcat, this technique is especially useful when the strings to be appended require printf-like formatting. Listing 2 illustrates the special case of an existing string that needs to be padded with blanks (before highlighting it for the user, say). It also illustrates how to specify a variable-length format in C. The * edit descriptor can appear as a width or a precision specifier. It indicates that the next argument in the print list (which must be an int) should be used as the field width (or precision). The preceding if statement is necessary, otherwise a single blank will always be printed, even if (size-len) is zero.

Specifying Variable-Length Format

Variable-length format is also useful when formatting reports or screen output. Instead of allowing long lines to wrap, one usually prefers to truncate them, and then provide some other way to view the suppressed data (like scrolling).The segment

#define WIDTH 80
. . .
fprintf(stderr, "%.*s",WIDTH,line);
displays a line on the screen without wrapping.

The statement

fprintf(f,"%*.*s\n",WIDTH,WIDTH,line);
right-justifies a row in a report.

Padding a String

While printf provides some support for zero and blank padding, it is often not enough. For example, in financial applications it is convenient to specify an arbitrary pad character, such as asterisks for check protection. Listing 3 shows a function, align, that places a string into a field with left, right or center justification and pads with a user-supplied character.

The function calls

align(s,10,'*',LEFT,"123.45")
align(s,10,'*',CENTER,"123.45")
align(s,10,'*',RIGHT,"123.45")
build, in turn, the strings

123.45****
**123.45**
****123.45
inside s [].

Specifying Variable Argument Lists

The printf functions process a variable number of arguments. An alternate set of functions, vprintf, vfprintf and vsprintf, allow the user to also write output functions that themselves do not need to know how many arguments are passed. A useful example is a function, fatal, which prints an error message and then exits the program (see Listing 4) . Some valid calls to fatal are

fatal("Divide by zero in function f()\n");
fatal("Invalid value in function foo: %d\n", x);
fatal("%s: %d\n",argv[0],error_code);
Like the printf functions, all that matters is that the arguments to be printed match the edit descriptors in the format string. The macro call va_start (args,fmt) causes the variable argument list args (which is just a pointer in most implementations) to point to the first argument after fmt. The function vfprintf behaves like fprintf except that it expects a va_list after the format string instead of hard-coded arguments. A call to va_end is required to clean up any implementation-defined loose ends before execution continues.

Using vsprintf, we can write a version of strcat (let's call it append) that does formatting (see Listing 5) .