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.