Skip to content

Worked examples

Short, self-contained programs suitable for adding to a project, building with scripts/ma.ps1, and running under an emulator such as ntvcm. See Building and linking for build options and the manual pipeline.

Sorting and searching an int array

qsort orders the array, then bsearch locates a key with the same comparator. The comparator returns negative / zero / positive — here the branchless (x > y) - (x < y) idiom.

static int cmp_int(const void *a, const void *b)
{
    int x = *(const int *)a;
    int y = *(const int *)b;
    return (x > y) - (x < y);
}

int main(void)
{
    int v[8];
    int key = 13;
    const int *hit;

    v[0] = 2; v[1] = 8; v[2] = 5; v[3] = 13;
    v[4] = 1; v[5] = 21; v[6] = 3; v[7] = 34;

    qsort(v, 8U, sizeof(int), cmp_int);     /* 1 2 3 5 8 13 21 34 */
    hit = (const int *)bsearch(&key, v, 8U, sizeof(int), cmp_int);
    if (hit)
        printf("found %d at index %d\n", *hit, (int)(hit - v));
    else
        puts("not found");
    return 0;
}

Output: found 13 at index 5.

Sorting an array of structs by a key field

Any element width works because qsort swaps whole elements byte-by-byte. The comparator reads the field it sorts on — here a string member via strcmp — and bsearch reuses it to look a record up by name.

struct item {
    char name[8];
    int  qty;
};

static int by_name(const void *a, const void *b)
{
    return strcmp(((const struct item *)a)->name,
                  ((const struct item *)b)->name);
}

int main(void)
{
    struct item items[3];
    struct item key;
    const struct item *hit;
    int i;

    strcpy(items[0].name, "pears");  items[0].qty = 4;
    strcpy(items[1].name, "apples"); items[1].qty = 9;
    strcpy(items[2].name, "kiwis");  items[2].qty = 2;

    qsort(items, 3U, sizeof(struct item), by_name);
    for (i = 0; i < 3; i++)
        printf("%-8s %d\n", items[i].name, items[i].qty);

    strcpy(key.name, "kiwis");
    hit = (const struct item *)bsearch(&key, items, 3U,
                                       sizeof(struct item), by_name);
    if (hit)
        printf("%s: %d in stock\n", hit->name, hit->qty);
    return 0;
}

Output:

apples   9
kiwis    2
pears    4
kiwis: 2 in stock

A printf-style logging wrapper

Forwarding a va_list to vfprintf supports custom diagnostic wrappers without re-parsing the arguments.

static void logmsg(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
}

int main(void)
{
    logmsg("ready: %d items, %lx flags\n", 3, 0xBEEFL);
    return 0;
}

Reading a text file line by line

int main(void)
{
    FILE *fp = fopen("DATA.TXT", "r");
    char  line[128];

    if (!fp) {
        perror("DATA.TXT");
        return 1;
    }
    while (fgets(line, sizeof line, fp))
        fputs(line, stdout);
    fclose(fp);
    return 0;
}

Parsing input with sscanf

sscanf reads from a string using the same conversion subset as scanf and fscanf (integers and strings; no floating input). Each conversion stores through a pointer argument.

int main(void)
{
    int  value;
    char word[16];
    int  hexval;
    long big;

    sscanf("-12 hello 0x2a", "%d %s %i", &value, word, &hexval);
    printf("value=%d word=%s hexval=%d\n", value, word, hexval);

    sscanf("123456", "%ld", &big);
    printf("big=%ld\n", big);
    return 0;
}

Output:

value=-12 word=hello hexval=42
big=123456

Buffered console output with a user-declared buffer

setvbuf allows supplying a user-allocated buffer for console output, so output accumulates instead of going to CP/M one character at a time. A larger buffer means fewer BDOS calls. Drain it with fflush, and detach it (setvbuf(stdout, NULL, _IOLBF, 0)) before the buffer's storage is reused — see Console output buffering.

int main(void)
{
    static char obuf[1024];   /* user-declared console buffer */
    int i;
    long total;

    /* Adopt obuf and fully buffer: output accumulates instead of going to the
     * BDOS one character at a time. */
    if (setvbuf(stdout, obuf, _IOFBF, sizeof obuf) != 0) {
        puts("setvbuf failed");
        return 1;
    }

    total = 0;
    for (i = 1; i <= 20; i = i + 1) {
        printf("row %2d: %ld\n", i, total);
        total = total + (long) i * i;
    }

    /* Nothing has reached the console yet (fully buffered, under 1 KB).
     * Drain it explicitly. */
    fflush(stdout);

    printf("sum of squares 1..20 = %ld\n", total);

    /* Flush before detaching: switching buffers while output is still
     * pending in the old one is unspecified behavior (some libc's drop it
     * instead of auto-flushing). */
    fflush(stdout);

    /* Detach the buffer before it goes out of scope / is reused, so the
     * automatic flush at exit uses the internal buffer. */
    setvbuf(stdout, (char *) 0, _IOLBF, 0);

    return 0;
}

Output ends with sum of squares 1..20 = 2870. The static buffer keeps it off the small CP/M stack; a malloc'd buffer works too, but free it only after detaching it from the stream. This snippet is pulled verbatim from the tests/tbufex.c regression test, so the documented code is exactly what is built and run by the suite.