Skip to content

Memory and utilities (stdlib.h)

Include stdlib.h. This header covers dynamic memory, string-to-number conversion, integer arithmetic helpers, searching and sorting, process control, and pseudo-random numbers.

Types and Macros

Name Meaning
NULL Null pointer constant.
EXIT_SUCCESS Successful program termination status.
EXIT_FAILURE Unsuccessful program termination status.
RAND_MAX Maximum value returned by rand.
ATEXIT_MAX Maximum number of functions that can be registered with atexit().
div_t Quotient and remainder pair returned by div.
ldiv_t Quotient and remainder pair returned by ldiv.
MB_CUR_MAX Maximum bytes in a multibyte character in the current locale.

Functions

Function Summary
_Noreturn void abort( void ) Terminate the program abnormally; does not call atexit handlers.
int abs(int j) Absolute value of a signed int.
int atexit( void (*func)(void) ) Register func to be called at normal program termination (LIFO order). Returns 0 on success, nonzero if the ATEXIT_MAX table is full.
float atof(const char *nptr) Convert the leading decimal text in nptr to float. Note: C89 atof normally returns double; dcc has no double type, so this returns float (IEEE 754 single precision). Accepts nan, inf, and infinity spellings. Overflow returns signed infinity; underflow returns signed zero.
int atoi(const char *nptr) Convert leading decimal text in nptr to int after skipping C whitespace.
long atol(const char *nptr) Convert leading decimal text in nptr to long after skipping C whitespace.
int bdos( int fn, int dearg ) Call the CP/M BDOS entry point.
int bdoshl( int fn, int dearg ) Call the CP/M BDOS entry point, returning the full HL result.
int bios( int fn, int dearg ) Call the CP/M BIOS jump table directly.
int bioshl( int fn, int dearg ) Call the CP/M BIOS jump table directly, returning the full HL result.
int biosreg( int fn, int bcarg, int dearg ) Call the CP/M BIOS with independent BC and DE arguments, returning HL.
void *bsearch(const void *key, const void *base, size_t num, size_t size, int (*compare)(const void *, const void *)) Binary-search a sorted array.
void *calloc( size_t num, size_t size ) Allocate and zero num * size bytes from the heap.
div_t div(int numer, int denom) Signed int division returning quotient and remainder.
int exec( const char *path, const char *cmdtail ) Load and run path, replacing this process. cmdtail is copied through its first NUL or CR into private staging before loader side effects, then to 0x81 (e.g. " ARG1 ARG2"). At most 127 text bytes are accepted; a trailing CR is written when space remains. The first two arguments, delimited by bytes through ASCII space, also seed the default FCBs at 0x5C and 0x6C. path must be an unambiguous CP/M 8.3 filename with an optional A: through P: drive prefix; ".COM" is appended when no extension is present. Returns -1 with errno=E2BIG for an oversized tail, EINVAL for an invalid path, ENOENT when the file cannot be opened, or EFBIG when its 128-byte-record-rounded image cannot fit safely in the TPA; does not return on success.
int execv( const char *path, char **argv ) Like exec() but builds the command tail from argv[1..] (argv[0] is the conventional program name and is ignored for CP/M purposes). argv must be a NULL-terminated array of string pointers. Because DCC's direct CP/M startup parser deliberately has no quoting/escape syntax, each argument must be nonempty and contain no byte through ASCII space; quote and backslash are ordinary bytes. Returns -1 with errno=EINVAL when an argument cannot round-trip, or errno=E2BIG above the 127-byte tail limit.
_Noreturn void exit( int code ) Terminate the program after flushing runtime output.
void free( void *ptr ) Release a heap allocation.
char *getenv(const char *name) Search the environment for name; always returns NULL on CP/M 2.2.
int inp( unsigned port ) Read an 8-bit Z80 I/O port.
long labs(long j) Absolute value of a signed long.
ldiv_t ldiv(long numer, long denom) Signed long division returning quotient and remainder.
void *malloc( size_t size ) Allocate size bytes from the heap.
int mblen(const char *s, size_t n) Length of the multibyte character at s, examining at most n bytes.
size_t mbstowcs(wchar_t *pwcs, const char *s, size_t n) Convert at most n multibyte characters from s into the wchar_t array pwcs.
int mbtowc(wchar_t *pwc, const char *s, size_t n) Convert the multibyte character at s into *pwc; examine at most n bytes.
void outp( unsigned port, unsigned val ) Write an 8-bit Z80 I/O port.
void qsort(void *base, size_t num, size_t size, int (*compare)(const void *, const void *)) Sort an array in place.
int rand(void) Return the next pseudo-random integer in the range 0 through RAND_MAX.
void *realloc( void *ptr, size_t size ) Resize a heap allocation, preserving contents up to the smaller size.
void srand(unsigned int seed) Seed the pseudo-random number generator.
float strtod(const char *nptr, char **endptr) Convert leading floating-point text in nptr to float; sets *endptr past consumed input. Note: C89 strtod returns double; dcc returns float (no double type). Numeric overflow returns signed infinity and underflow returns signed zero; both set errno to ERANGE. Explicit inf/nan spellings are not range errors.
long strtol(const char *nptr, char **endptr, int base) Convert text in nptr to long using base 2 through 36, or base 0 for auto-detection.
unsigned long strtoul(const char *nptr, char **endptr, int base) Convert text in nptr to unsigned long using base 2 through 36, or base 0 for auto-detection.
int system(const char *string) Execute a shell command. CP/M 2.2 has no command processor: system(NULL) correctly reports that via 0 (false), and any non-NULL command returns -1 (unsupported).
size_t wcstombs(char *s, const wchar_t *pwcs, size_t n) Convert at most n bytes from pwcs into s; return (size_t)-1 on an unrepresentable wide character.
int wctomb(char *s, wchar_t wc) Convert wc into one byte at s, or return -1 if wc is unrepresentable.

Runtime model

The standard functions in this header are runtime-backed. DCC C Compiler also declares a small set of CP/M and Z80 extensions here (BDOS/BIOS wrappers, port I/O, and program replacement); those are documented with the CP/M services rather than treated as portable C APIs.

Dynamic memory

The allocator uses a first-fit heap walk with two-byte packed boundary tags at the start and end of each block. Freeing a block coalesces it with adjacent free neighbours (including blocks freed via realloc(p, 0) and the old block released by a growing realloc), which keeps fragmentation down. realloc also grows in place at the heap top or into an immediately following free block. The heap grows on demand between the end of BSS and the stack. On CP/M this space is bounded by the program's TPA: code, data, runtime support, heap, and stack all share the same transient program area.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char *buffer = malloc(256);
    char *resized;

    if (!buffer)
        return EXIT_FAILURE;
    resized = realloc(buffer, 512);
    if (!resized) {
        free(buffer);
        return EXIT_FAILURE;
    }
    buffer = resized;
    free(buffer);
    return EXIT_SUCCESS;
}

realloc follows the standard rules: realloc(NULL, n) behaves like malloc(n), and realloc(p, 0) frees p and returns NULL.

Allocation failure returns NULL; a failed nonzero realloc leaves the original block valid. Do not overwrite your only pointer before checking the result. free(NULL) does nothing. Zero-byte malloc requests use the minimum allocation size and can still fail. calloc rejects a product that exceeds 16-bit size_t; check multiplication before calling malloc(count * size), where the expression itself can already have wrapped. Allocation failure does not guarantee that errno is set.

Size cost

calloc uses checked size multiplication and zero-filling in addition to allocation. strdup also requires the allocator. See the appendix.

Conversion

atoi/atol skip the full C whitespace set (space and bytes \t through \r), accept an optional +/- sign, then consume decimal digits; conversion stops at the first non-digit. Overflow wraps modulo the type width.

int  n = atoi("  -123xyz");   /* -123  */
long m = atol("  -123456");   /* -123456L */

strtol/strtoul are the full C89 conversions. They skip leading whitespace, accept an optional sign, honour a 0x/0X prefix for base 16 and a leading 0 for base 8 when base is 0, and accept digits/letters up to base-1 for any base from 2 to 36. The unused tail is reported through *end when end is non-NULL. On overflow they clamp to LONG_MAX/LONG_MIN (or ULONG_MAX) and set errno to ERANGE. A 0x prefix is recognized only when a hexadecimal digit follows it; for "0x" or "0xG" the leading zero is converted and end points at the x, without changing errno.

char *end;
long  v = strtol("  -0x1Ag", &end, 0);            /* v = -26, *end = 'g'  */
unsigned long u = strtoul("4294967295", NULL, 10); /* ULONG_MAX */

atof is available as a DCC C Compiler extension: it is declared as float atof(const char *nptr) and returns IEEE 754 single precision. C89 atof normally returns double, which DCC C Compiler does not have. It accepts ordinary decimal text with an optional exponent, plus the case-insensitive spellings nan, inf, and infinity. Overflow returns signed infinity; underflow returns signed zero. strtod uses the same parser and reports the first unconsumed byte through endptr. Numeric overflow and underflow set errno to ERANGE; explicit infinity/NaN spellings and an exact zero with a large exponent are not range errors.

For checked input, prefer strtol/strtoul/strtod to atoi/atol/atof. Set errno = 0 before conversion, check whether endptr advanced, inspect any unconsumed suffix, and check ERANGE. A zero result alone cannot distinguish valid zero from failed conversion.

Multibyte and wide characters

DCC uses a fixed single-byte execution encoding (MB_CUR_MAX == 1). Byte values 0x00 through 0xFF map to equal-valued 16-bit wchar_t values. A wider value is unrepresentable: wctomb returns -1 without writing a truncated byte, and wcstombs returns (size_t)-1 at the offending element. wcstombs may already have stored a representable prefix, as permitted by C, but never stores the truncated offending value. With n == 0, it examines and writes no elements and returns zero.

Integer arithmetic helpers

div returns a div_t with quot and rem members; ldiv returns an ldiv_t with 32-bit members. Signed division truncates toward zero; the remainder has the same sign as the numerator.

div_t  d  = div(-7, 3);          /* d.quot == -2, d.rem == -1 */
ldiv_t ld = ldiv(200000L, 7L);

Searching and sorting

Both take the standard comparator: cmp(a, b) returns negative if a sorts before b, zero if equal, positive if after. qsort uses an in-place, non-recursive Shell sort, so it is not stable; bsearch requires the array to be sorted by the same comparator. See Worked examples for complete programs.

Process control

exit(status) and returning from main run registered atexit functions in reverse registration order, clean up temporary files, and flush console output. atexit returns zero on success and nonzero when its 32-entry table is full. Close ordinary files explicitly before exiting. abort flushes console output and warm-boots without running handlers or temporary-file cleanup; it does not set an exit status.

CP/M has no environment-variable service: getenv always returns NULL. There is no supported returning shell-command service: system(NULL) returns zero and system(command) returns -1. Use the DCC-specific exec functions below only when replacing the current program is intended.

The exit code is surfaced through CP/M 3.0 BDOS call 108, which emulators such as ntvcm reflect in their own process exit code. Returning a value from main has the same effect.

exec() and execv() replace the current program through the CP/M command-tail area. DCC accepts the full 127-byte payload in 0x81..0xFF; the length byte is authoritative, so the conventional trailing CR is omitted only at that exact maximum. A 128th byte returns -1 with errno == E2BIG. exec() validates and copies its caller-owned source into private staging before opening the image, abandoning the caller stack, clearing FCBs, or writing the destination tail, so stack-local and overlapping low-memory sources are safe.

The executable path must be an unambiguous CP/M 8.3 name: an optional A: through P: drive, one to eight filename bytes, and an optional one to three byte filetype. DCC appends .COM when the filetype is absent. Invalid syntax returns EINVAL, a failed open returns ENOENT, and an executable whose 128-byte-record-rounded image cannot fit below the loader's reserved high-memory stack/FCB/trampoline ranges returns EFBIG.

The BDOS function 35 record count is also the loader's exact read contract. The high-memory trampoline performs exactly that many successful sequential reads, never reads a newly grown extra record, and warm-boots rather than jumping to a partial image if any approved read returns a nonzero status.

The startup parser intentionally keeps direct CP/M behavior: bytes through ASCII space delimit arguments, while quote and backslash are literal bytes and have no escaping role. Therefore execv() can round-trip only nonempty arguments containing bytes above ASCII space. It returns -1 with errno == EINVAL for empty arguments or arguments containing spaces, tabs, or other delimiter bytes rather than silently changing argv.

The first two command-tail words seed CP/M's default FCB1 and FCB2. Their delimiter rule is identical to startup argument parsing: every byte through ASCII space, including tab and control whitespace, is a delimiter.

Pseudo-random numbers

RAND_MAX is 32767.

srand(1);
int roll = rand() % 6 + 1;     /* a die roll */

The runtime generator is a 16-bit xorshift with parameters 7, 9, and 8. The state is 16-bit, srand(seed) stores the state directly, and rand() clears bit 15 of the updated state so the result stays in the C89 0 .. RAND_MAX range. In C-equivalent form:

static unsigned int s_rnd = 1;

void srand(unsigned int seed)
{
    s_rnd = seed;
}

int rand(void)
{
    s_rnd ^= s_rnd << 7;
    s_rnd ^= s_rnd >> 9;
    s_rnd ^= s_rnd << 8;
    return (int)(s_rnd & 0x7fff);
}

That deterministic sequence is useful for benchmarks: if another CP/M compiler uses the same C equivalent, tests that depend on rand() can compare runtime library and code-generation performance without being skewed by different pseudo-random sequences.