Exercise 7-3. Revise
minprintfto handle more of the other facilities ofprintf.
K&R’s minprintf (Section 7.3) handles only %d and %s. The cleanest way to extend it: as we parse the format string, collect the complete format specifier (flags, width, precision, length modifier, conversion character) into a local buffer, then call printf with that exact sub-format and the right argument — so we delegate the actual formatting to the C library and only do the parsing ourselves.
Solution
/* K&R Exercise 7-3 — extended minprintf
* Handles: d, i, o, x, X, u, c, s, f, e, E, g, G, p, %
* and length modifiers: l, h
* Compile: gcc -ansi -Wall ex7-3.c -o ex7-3 */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
void minprintf(const char *fmt, ...)
{
va_list ap;
const char *p;
char subfmt[64]; /* holds one complete format specifier */
char *q;
va_start(ap, fmt);
for (p = fmt; *p; p++) {
if (*p != '%') {
putchar(*p);
continue;
}
/* Collect the full specifier into subfmt[]:
* flags, width, precision, length modifier, conversion */
q = subfmt;
*q++ = '%';
/* flags: -, +, space, 0, # */
while (strchr("-+ 0#", p[1]))
*q++ = *++p;
/* width (digits or *) */
if (p[1] == '*') {
*q++ = *++p;
} else {
while (p[1] >= '0' && p[1] <= '9')
*q++ = *++p;
}
/* precision */
if (p[1] == '.') {
*q++ = *++p; /* '.' */
if (p[1] == '*') {
*q++ = *++p;
} else {
while (p[1] >= '0' && p[1] <= '9')
*q++ = *++p;
}
}
/* length modifier: l or h */
if (p[1] == 'l' || p[1] == 'h')
*q++ = *++p;
/* conversion character */
*q++ = *++p;
*q = '\0';
switch (*p) {
case 'd': case 'i':
if (*(q-2) == 'l')
printf(subfmt, va_arg(ap, long));
else
printf(subfmt, va_arg(ap, int));
break;
case 'o': case 'u': case 'x': case 'X':
if (*(q-2) == 'l')
printf(subfmt, va_arg(ap, unsigned long));
else
printf(subfmt, va_arg(ap, unsigned int));
break;
case 'c':
printf(subfmt, va_arg(ap, int));
break;
case 's':
printf(subfmt, va_arg(ap, char *));
break;
case 'f': case 'e': case 'E': case 'g': case 'G':
printf(subfmt, va_arg(ap, double));
break;
case 'p':
printf(subfmt, va_arg(ap, void *));
break;
case '%':
putchar('%');
break;
default:
putchar('%');
putchar(*p);
break;
}
}
va_end(ap);
}
int main(void)
{
int n = 42;
long ln = 1234567890L;
double d = 3.14159265;
char *s = "hello";
minprintf("decimal: %d\n", n);
minprintf("octal: %o\n", n);
minprintf("hex: %x %X\n", n, n);
minprintf("unsigned: %u\n", (unsigned)n);
minprintf("long: %ld\n", ln);
minprintf("float: %f\n", d);
minprintf("sci: %e\n", d);
minprintf("g-format: %g\n", d);
minprintf("width: %10d\n", n);
minprintf("prec: %.4f\n", d);
minprintf("left: %-10s|\n", s);
minprintf("char: %c\n", 'A');
minprintf("percent: 100%%\n");
return 0;
}
Compile and Run
gcc -ansi -Wall ex7-3.c -o ex7-3
./ex7-3
Expected Output
decimal: 42 octal: 52 hex: 2a 2A unsigned: 42 long: 1234567890 float: 3.141593 sci: 3.141593e+00 g-format: 3.14159 width: 42 prec: 3.1416 left: hello | char: A percent: 100%
How It Works
The key idea — collect then delegate:
/* Instead of formatting ourselves, reconstruct the specifier and
* pass it to printf with the right argument type */
printf(subfmt, va_arg(ap, int));
This avoids reimplementing floating-point formatting, padding, precision, etc. We only parse the specifier to extract the argument type; the library does the rest.
What This Exercise Teaches
- Variadic functions with
va_list—va_start,va_arg, andva_endare the three-step protocol; everyva_argcall must specify the exact promoted type that the caller passed - Argument promotion rules —
charandshortare promoted toint;floatis promoted todouble; this is why%cusesva_arg(ap, int), notva_arg(ap, char) - Parsing before dispatch — collecting the full specifier into
subfmt[]first, then switching on the conversion character, separates "understanding the format" from "using the argument" cleanly
Set Up Your C Environment
← Exercise 7-2 |
Chapter 7 Solutions |
Exercise 7-4 →
Book: The C Programming Language, 2nd Ed — Kernighan & Ritchie