Jump to: navigation, search

The C Programming Language, 2nd Edition, by Kernighan and Ritchie
Exercise 7.04 on page 159

Write a private version of scanf analogous to minprintf from the previous section.



Warning: the answers on these pages are crowdsourced, open to anybody to provide/edit after free sign-up, have not all been vetted, might not be correct, let alone optimal, and should not be assumed to be.

Solution by Thomas Amundsen

It seems that the real scanf doesn't handle floats and strings the way I expect it to. Having said that, the book's example of a rudimentary calculator on page 141 does not work on my machine.

Here is a solution that only handles integers.

/* Thomas Amundsen - K&R2 Exercise 7-4 - 2009-06-19 */

#include <stdio.h>
#include <stdarg.h>

void minscanf(char *fmt, ...);

int main()
{
  int i;

  minscanf("%d", &i); /* scan integer from stdin */
  printf("scanned %d\n", i); /* print scanning results to stdout */
  
  return 0;
}

/* minscanf: minimal scanf with variable argument list
   only scans integers */
void minscanf(char *fmt, ...)
{
  va_list ap; /* points to each unnamed arg in turn */
  char *p;
  int *ival;

  va_start(ap, fmt); /* make ap point to 1st unnamed arg */

  for (p = fmt; *p; p++) {

    /* skip chars that aren't format conversions */
    if (*p != '%')
      continue;

    /* prev char was %, look for format conversion */
    switch(*++p) {
    case 'd':
      ival = va_arg(ap, int *); /* get integer pointer from args */
      scanf("%d", ival); /* read integer into int pointer */
      break;
     default:
      break;
    }
  }
}


Solution by codybartfast (cat 0)

Supports %d, %f, %s and %%.

#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>

int minscanf(char *str, char *fmt, ...);

int minscanf(char *str, char *fmt, ...)
{
	va_list ap;
	int matched = 0;
	char *p;

	va_start(ap, fmt);
	for (; *fmt; fmt++) {
		if (isspace(*fmt))
			continue;
		for (; isspace(*str); str++)
			;
		if (*str == '\0')
			break;
		if (*fmt != '%') {
			if (*fmt == *str) {
				str++;
				continue;
			} else {
				break;
			}
		}

		switch (*++fmt) {
		case 'd':
			if (!isdigit(*str) && !((*str == '+' || *str == '-') &&
						isdigit(*(str + 1))))
				break;
			*va_arg(ap, int *) = atoi(str);
			str++;
			while (isdigit(*str) && *str)
				str++;
			matched++;
			continue;
		case 'f':
			*va_arg(ap, double *) = strtod(str, &p);
			if (p == str)
				break;
			str = p;
			matched++;
			continue;
		case 's':
			if (isspace(*str) || !*str)
				break;
			p = va_arg(ap, char *);
			while (!isspace(*str) && *str)
				*p++ = *str++;
			*p = '\0';
			matched++;
			continue;
		case '%':
			if (*str == '%') {
				str++;
				continue;
			} else {
				break;
			}
		default:
			break;
		}
		break;
	}
	va_end(ap);
	return matched;
}

Solution by anonymous

I implemented all of the basic format specifiers for C that I could find except for %n. Also, I didn't include support for field width, length modifiers, assignment suppression, and the [...] and [^…] character sets.

#include <stdio.h>
#include <stdarg.h>

/*
    Exercise 7-4. Write a private version of scanf analogous to minprintf from the previous section.
*/

int minscanf(char *fmt, ...);

int main()
{
    char s[3], c;
    int i1, i2, i3, i4, i5, i6, i7, i8, i9, i10;
    float f1, f2, f3, f4, f5;
    unsigned int u;
    void *p = NULL;
    printf("Copy and paste in the following text to test this program:\n-5 0x6 0X7 7 010 11 11 c D 0xe 0XFg hi 1.920e+001 2.122E+001 2.3 2.4 2.5 %p %%\n", (void *) &c);
    if (minscanf("%d %i %i %i %o %o %u %x %x %x %x %c %s %e %e %f %g %g %p %%", &i1, &i2, &i3, &i4, &i5, &i6, &u, &i7, &i8, &i9, &i10, &c, s, &f1, &f2, &f3, &f4, &f5, &p) != 19)
        printf("Not all inputs were assigned!\n");
    printf("%d %#x %#X %o %#o %o %u %x %X %#x %#X%c %s %.3e %.3E %.1f %g %g %p %%\n", i1, i2, i3, i4, i5, i6, u, i7, i8, i9, i10, c, s, f1, f2, f3, f4, f5, p);
    return 0;
}

// minimal scanf with variable argument list. Returns number of assigned input items
int minscanf(char *fmt, ...)
{
    va_list ap; // points to each unnamed arg in turn
    char *p, *sval, format[3] = { '%', '\0', '\0' };
    int *ival, numFound = 0;
    double *dval;
    unsigned int *uival;
    void **vval; // pointer to a pointer

    va_start(ap, fmt); // make ap point to 1st unnamed arg
    for (p = fmt; *p; p++)
    {
        if (*p != '%')
            continue;
        switch (*++p)
        {
        case 'd': case 'i': case 'c': // char is promoted to int when passed through '...'
            ival = va_arg(ap, int *);
            format[1] = *p;
            numFound += scanf(format, ival);
            break;
        case 'e': case 'f': case 'g':
            dval = va_arg(ap, double *);
            format[1] = *p;
            numFound += scanf(format, dval);
            break;
        case 's':
            sval = va_arg(ap, char *);
            numFound += scanf("%s", sval);
            break;
        case 'x': case 'o':  case 'u':
            uival = va_arg(ap, unsigned int *);
            format[1] = *p;
            numFound += scanf(format, uival);
            break;
        case 'p':
            vval = va_arg(ap, void *);
            numFound += scanf("%p", vval);
        case '%': // no need to do anything
            break;
        default: 
            printf("unsupported format specifier: %%%c\n", *p);
            break;
        }
    }
    va_end(ap); // clean up when done
    return numFound;
}
Personal tools