Worked examples¶
Short, self-contained programs suitable for adding to a project, building with
dccmake, and running under an emulator such as ntvcm. For example, from the
DCC checkout:
dccmake tests/texsort.c dcc-output=TEXSORT
ntvcm build/TEXSORT.COM
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.
Waiting for an I/O-adapter timer¶
The example debugger I/O adapter provides a 16-bit millisecond timer on ports 24 and 25. Write the delay's high byte to port 24, then its low byte to port 25 to start the timer. Reading either port returns 1 while it is running and 0 after it expires.
#include <stdio.h>
#include <stdlib.h>
#define TIMER_0_HIGH 24
#define TIMER_0_LOW 25
static void start_timer(unsigned delay_ms)
{
outp(TIMER_0_HIGH, delay_ms >> 8);
outp(TIMER_0_LOW, delay_ms & 0xff);
}
int main(void)
{
puts("Waiting for one second...");
start_timer(1000U);
while (inp(TIMER_0_LOW) != 0)
;
puts("Timer expired.");
return 0;
}
This program requires dcc-debug-host with the example I/O adapter loaded;
ordinary emulators need an equivalent device on those ports. The source lives
beside the adapter and builds with:
./dccmake src/dcc_debug_host/examples/io_adapter/timer.c dcc-output=TIMER
See Direct port I/O for the inp and
outp runtime contract.
Handling periodic I/O-adapter interrupts¶
The example adapter also provides a periodic maskable-interrupt source on port 52. Writing a value from 1 through 255 selects that many interrupts per second; writing zero disables the source and clears pending requests. Reading the port returns the configured rate.
This example installs a Z80 interrupt mode 1 vector at 0038H, waits with
HALT, counts 50 interrupts at 10 Hz in a C function, then disables the source
and restores CP/M's original vector:
#include <stdio.h>
#define TARGET_TICKS 50
volatile unsigned int irq_ticks;
extern void irq_install(volatile unsigned int *counter, void (*handler)(void));
extern void irq_wait(void);
extern unsigned int irq_count(void);
extern void irq_remove(void);
void irq_tick(void)
{
irq_ticks++;
}
#asm
public _irq_install
public _irq_wait
public _irq_count
public _irq_remove
INTVEC equ 0038h
INTPORT equ 52
; Save CP/M's vector and bind the C state passed through the normal dcc ABI:
; IX+4/5 = counter pointer, IX+6/7 = handler function pointer.
_irq_install:
di
push ix
ld ix,0
add ix,sp
ld l,(ix+4)
ld h,(ix+5)
ld (irq_counter),hl
ld l,(ix+6)
ld h,(ix+7)
ld (irq_call+1),hl
pop ix
ld a,(INTVEC)
ld (irq_old),a
ld a,(INTVEC+1)
ld (irq_old+1),a
ld a,(INTVEC+2)
ld (irq_old+2),a
ld a,0c3h
ld (INTVEC),a
ld hl,irq_isr
ld (INTVEC+1),hl
im 1
ld a,10
out (INTPORT),a
ei
ret
; Sleep until the next accepted interrupt. The ISR resumes after HALT.
_irq_wait:
halt
ret
; Return an atomic 16-bit snapshot in HL using the normal dcc return ABI.
_irq_count:
di
ld hl,(irq_counter)
ld e,(hl)
inc hl
ld d,(hl)
ex de,hl
ei
ret
; Stop the source before restoring CP/M's original three vector bytes.
_irq_remove:
di
xor a
out (INTPORT),a
ld a,(irq_old)
ld (INTVEC),a
ld a,(irq_old+1)
ld (INTVEC+1),a
ld a,(irq_old+2)
ld (INTVEC+2),a
ret
; Preserve all visible Z80 register sets before entering ordinary C.
irq_isr:
push af
push bc
push de
push hl
push ix
push iy
ex af,af'
push af
ex af,af'
exx
push bc
push de
push hl
exx
irq_call:
call 0000h
exx
pop hl
pop de
pop bc
exx
ex af,af'
pop af
ex af,af'
pop iy
pop ix
pop hl
pop de
pop bc
pop af
ei
reti
irq_counter:
dw 0
irq_old:
ds 3
#endasm
int main(void)
{
unsigned int count;
irq_ticks = 0;
printf("DCC C + Z80 interrupt example\n");
printf("Counting %u timer interrupts at 10 Hz...\n", TARGET_TICKS);
irq_install(&irq_ticks, irq_tick);
do {
irq_wait();
count = irq_count();
} while (count < TARGET_TICKS);
irq_remove();
printf("C handler count: %u\n", count);
return count == TARGET_TICKS ? 0 : 1;
}
The assembly wrapper preserves both Z80 register sets before calling C. The C handler only updates memory: interrupt code must not call BDOS, perform console I/O, allocate memory, or use other non-reentrant runtime services.
Build the source with debug metadata using:
./dccmake -g src/dcc_debug_host/examples/io_adapter/dccint.c \
dcc-output=DCCINT
Run DCCINT.COM under dcc-debug-host with the example I/O adapter loaded.
It counts for five seconds, prints C handler count: 50, restores the vector, and
returns normally to CP/M.