K&R C Programs Exercise 7-3

Exercise 7-3. Revise minprintf to handle more of the other facilities of printf.

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_listva_start, va_arg, and va_end are the three-step protocol; every va_arg call must specify the exact promoted type that the caller passed
  • Argument promotion ruleschar and short are promoted to int; float is promoted to double; this is why %c uses va_arg(ap, int), not va_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

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>