7-3 exercise

2026-10-14

#include <stdio.h>
#include <stdarg.h>     //va_list, va_start, va_arg, va_end
#include <string.h>     //strchr
//compile with: gcc -Wall -Wextra cc-c.c -o cc-c
//run with:     ./cc-c

//K&R2 exercise 7-3: minprintf revised to handle more of printf.
//Instead of re-implementing every conversion by hand, each %-spec is
//copied into a small local format string ("%-8.3f", "%lx", ...) and
//handed to the real printf together with ONE argument, fetched with
//va_arg as the right type. minprintf's job is now just the parsing:
//which characters make up the spec, and which type the conversion eats.
//
//Handled: flags - + space # 0, width, .precision, * for width or
//precision taken from the argument list, the l (long) modifier, and
//conversions d i o x X u c s f e E g G p %.

#define LOCALFMT 100    //room for one conversion spec

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

int main(void)
{
    //each line: real printf first, then minprintf with the same args
    printf(    "[%d] [%5d] [%-5d] [%05d] [%+d]\n", 42, 42, 42, 42, 42);
    minprintf( "[%d] [%5d] [%-5d] [%05d] [%+d]\n", 42, 42, 42, 42, 42);
    printf(    "[%o] [%#o] [%x] [%#X] [%u]\n", 255, 255, 255, 255, 255u);
    minprintf( "[%o] [%#o] [%x] [%#X] [%u]\n", 255, 255, 255, 255, 255u);
    printf(    "[%ld] [%lx]\n", 1234567890123L, 0xdeadbeefcafeUL);
    minprintf( "[%ld] [%lx]\n", 1234567890123L, 0xdeadbeefcafeUL);
    printf(    "[%f] [%.2f] [%10.3f] [%e] [%g]\n", 3.14159, 3.14159, 3.14159, 31415.9, 0.0001);
    minprintf( "[%f] [%.2f] [%10.3f] [%e] [%g]\n", 3.14159, 3.14159, 3.14159, 31415.9, 0.0001);
    printf(    "[%c] [%s] [%10s] [%-10s] [%.3s]\n", 'A', "hello", "hello", "hello", "hello");
    minprintf( "[%c] [%s] [%10s] [%-10s] [%.3s]\n", 'A', "hello", "hello", "hello", "hello");
    printf(    "[%*d] [%-*.*f] [100%%]\n", 6, 42, 9, 2, 2.71828);
    minprintf( "[%*d] [%-*.*f] [100%%]\n", 6, 42, 9, 2, 2.71828);
    return 0;
}

//minprintf: minimal printf with variable argument list
void minprintf(char *fmt, ...)
{
    va_list ap;                         //points to each unnamed arg in turn
    char *p;
    char localfmt[LOCALFMT];
    int i;
    int islong;

    va_start(ap, fmt);                  //make ap point to 1st unnamed arg
    for (p = fmt; *p; p++) {
        if (*p != '%') {
            putchar(*p);
            continue;
        }
        i = 0;
        localfmt[i++] = '%';

        //flags, width, precision: copy them through as they are.
        //A '*' means "the next int argument", so fetch it now and
        //write its digits into localfmt in place of the '*'.
        //The bound on i leaves room for the digits of one int plus
        //the modifier, the conversion, and the '\0'
        while (*(p+1) && strchr("-+ #0123456789.*", *(p+1))
               && i < LOCALFMT - 16)
            if (*++p == '*')
                i += sprintf(localfmt + i, "%d", va_arg(ap, int));
            else
                localfmt[i++] = *p;

        islong = 0;
        if (*(p+1) == 'l') {
            localfmt[i++] = *++p;
            islong = 1;
        }
        localfmt[i++] = *++p;           //the conversion character
        localfmt[i] = '\0';

        //IDIOM: case labels stacked with no break -- several values
        //share one body. Each group is "conversions that eat this type"
        switch (*p) {
        case 'd': case 'i':
            if (islong)
                printf(localfmt, va_arg(ap, long));
            else
                printf(localfmt, va_arg(ap, int));
            break;
        case 'o': case 'x': case 'X': case 'u':
            if (islong)
                printf(localfmt, va_arg(ap, unsigned long));
            else
                printf(localfmt, va_arg(ap, unsigned int));
            break;
        case 'c':                       //a char arrives promoted to int
            printf(localfmt, va_arg(ap, int));
            break;
        case 'f': case 'e': case 'E': case 'g': case 'G':
            printf(localfmt, va_arg(ap, double));   //floats arrive as double
            break;
        case 's':
            printf(localfmt, va_arg(ap, char *));
            break;
        case 'p':
            printf(localfmt, va_arg(ap, void *));
            break;
        case '%':
            putchar('%');
            break;
        case '\0':                      //fmt ended inside a spec, e.g. "50%":
            localfmt[i-1] = '\0';       //print what was collected, and stop
            fputs(localfmt, stdout);    //before p++ steps past the '\0'
            va_end(ap);
            return;
        default:                        //unknown conversion: print it as text
            fputs(localfmt, stdout);
            break;
        }
    }
    va_end(ap);                         //clean up when done
}

win@DESKTOP-MEIH88T:~/webdev-projects$ gcc -Wall -Wextra cc-c.c -o cc-c
win@DESKTOP-MEIH88T:~/webdev-projects$ ./cc-c
[42] [   42] [42   ] [00042] [+42]
[42] [   42] [42   ] [00042] [+42]
[377] [0377] [ff] [0XFF] [255]
[377] [0377] [ff] [0XFF] [255]
[1234567890123] [deadbeefcafe]
[1234567890123] [deadbeefcafe]
[3.141590] [3.14] [     3.142] [3.141590e+04] [0.0001]
[3.141590] [3.14] [     3.142] [3.141590e+04] [0.0001]
[A] [hello] [     hello] [hello     ] [hel]
[A] [hello] [     hello] [hello     ] [hel]
[    42] [2.72     ] [100%]
[    42] [2.72     ] [100%]