K&R C Programs Exercise 7-4

Exercise 7-4. Write a private version of scanf analogous to the private printf described in this section.

Like minprintf, minscanf parses a format string and uses va_list to accept a variable number of pointer arguments. It reads from stdin and dispatches to the appropriate conversion — using scanf itself with a constructed sub-format string, so we leverage the library for the actual parsing.

Conversions supported: %d, %i, %o, %x, %u, %f, %e, %g, %s, %c, %ld, %lf.

Solution

/* K&R Exercise 7-4 — minscanf: private scanf using va_list
 * Compile: gcc -ansi -Wall ex7-4.c -o ex7-4 */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

int minscanf(const char *fmt, ...)
{
    va_list ap;
    const char *p;
    char subfmt[64];
    char *q;
    int  count = 0;   /* number of successful conversions */
    int  ret;

    va_start(ap, fmt);

    for (p = fmt; *p; p++) {
        if (*p != '%') {
            /* literal character: match and consume from stdin */
            int c = getchar();
            if (c == EOF)  { va_end(ap); return count ? count : EOF; }
            if (c != *p)   { ungetc(c, stdin); va_end(ap); return count; }
            continue;
        }

        /* build sub-format specifier */
        q = subfmt;
        *q++ = '%';

        /* optional width */
        while (p[1] >= '0' && p[1] <= '9')
            *q++ = *++p;

        /* length modifier */
        if (p[1] == 'l' || p[1] == 'h')
            *q++ = *++p;

        /* conversion character */
        *q++ = *++p;
        *q   = '\0';

        ret = 0;
        switch (*p) {
        case 'd': case 'i':
            if (*(q-2) == 'l')
                ret = scanf(subfmt, va_arg(ap, long *));
            else
                ret = scanf(subfmt, va_arg(ap, int *));
            break;
        case 'o': case 'u': case 'x': case 'X':
            if (*(q-2) == 'l')
                ret = scanf(subfmt, va_arg(ap, unsigned long *));
            else
                ret = scanf(subfmt, va_arg(ap, unsigned int *));
            break;
        case 'f': case 'e': case 'g':
            if (*(q-2) == 'l')
                ret = scanf(subfmt, va_arg(ap, double *));
            else
                ret = scanf(subfmt, va_arg(ap, float *));
            break;
        case 's':
            ret = scanf(subfmt, va_arg(ap, char *));
            break;
        case 'c':
            ret = scanf(subfmt, va_arg(ap, char *));
            break;
        case '%':
            /* match a literal % */
            { int c = getchar();
              if (c == '%') ret = 1;
              else { ungetc(c, stdin); ret = 0; }
            }
            break;
        default:
            break;
        }

        if (ret == EOF) { va_end(ap); return count ? count : EOF; }
        if (ret == 1)   count++;
    }

    va_end(ap);
    return count;
}

int main(void)
{
    int   n;
    float f;
    char  s[64];

    printf("Enter an int, a float, and a word (e.g.: 42 3.14 hello):\n");
    if (minscanf("%d %f %s", &n, &f, s) == 3)
        printf("Read: n=%d  f=%.2f  s=%s\n", n, f, s);
    else
        printf("minscanf: conversion error\n");
    return 0;
}

Compile and Test

gcc -ansi -Wall ex7-4.c -o ex7-4
echo "42 3.14 hello" | ./ex7-4

Expected Output

Enter an int, a float, and a word (e.g.: 42 3.14 hello):
Read: n=42  f=3.14  s=hello

Design Notes

  • Delegate to scanf — just as minprintf delegates to printf, minscanf delegates each conversion to scanf with a reconstructed sub-format. This avoids reimplementing number parsing.
  • Return value convention — real scanf returns the count of successful conversions, or EOF on end-of-file before any conversion. minscanf follows the same convention.
  • float * vs double *scanf("%f", ptr) expects a float *; scanf("%lf", ptr) expects a double *. This is the opposite of printf, where both %f and %lf take a promoted double.

What This Exercise Teaches

  • Pointer arguments in variadic functionsscanf-style functions receive pointer-to-type arguments; va_arg(ap, int *) retrieves the address that the caller passed, and the conversion writes through it
  • Symmetry between printf and scanf — both parse the same format mini-language; the difference is direction: printf reads typed values and formats them, scanf parses text and stores typed values
  • Literal character matching — format characters outside % in a scanf format must match exactly (whitespace in the format matches any amount of whitespace in the input); this is why minscanf consumes and compares literal characters

Set Up Your C Environment

← Exercise 7-3  | 
Chapter 7 Solutions  | 
Exercise 7-5 →

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>