Exercise 7-4. Write a private version of
scanfanalogous to the privateprintfdescribed 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 asminprintfdelegates toprintf,minscanfdelegates each conversion toscanfwith a reconstructed sub-format. This avoids reimplementing number parsing. - Return value convention — real
scanfreturns the count of successful conversions, orEOFon end-of-file before any conversion.minscanffollows the same convention. float *vsdouble *—scanf("%f", ptr)expects afloat *;scanf("%lf", ptr)expects adouble *. This is the opposite ofprintf, where both%fand%lftake a promoteddouble.
What This Exercise Teaches
- Pointer arguments in variadic functions —
scanf-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:
printfreads typed values and formats them,scanfparses text and stores typed values - Literal character matching — format characters outside
%in ascanfformat must match exactly (whitespace in the format matches any amount of whitespace in the input); this is whyminscanfconsumes 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