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. |
meminfo_t |
DCC memory snapshot: 16-bit addresses and byte counts in the current address space. |
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 meminfo(meminfo_t *info) |
DCC extension: fill a non-NULL caller-owned snapshot without allocating. Committed bytes include allocator metadata and reusable free blocks below the break; growth is limit minus break, not total malloc-available space. |
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 runtime, CP/M and Z80 extensions here (memory snapshots, BDOS/BIOS wrappers, port I/O, and program replacement). These are not portable C APIs; hardware-facing calls are documented with the CP/M services.
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.
Memory layout snapshot (DCC extension)¶
void meminfo(meminfo_t *info) fills a caller-owned structure without allocating
memory, walking the allocator or changing errno. Pass a non-NULL pointer to a
valid meminfo_t. All seven fields are 16-bit unsigned int values:
| Field | Meaning |
|---|---|
heap_start |
Linked heap base, after the image, BSS and any startup scratch |
heap_limit |
Initialized exclusive heap ceiling |
heap_break |
Current exclusive end of the committed heap |
stack_size |
Configured stack reserve in bytes, not measured stack usage |
heap_capacity |
heap_limit - heap_start |
heap_committed |
heap_break - heap_start |
heap_growth |
heap_limit - heap_break |
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
meminfo_t info;
meminfo(&info);
printf("Heap: %u capacity, %u committed, %u growth; stack: %u\n",
info.heap_capacity, info.heap_committed,
info.heap_growth, info.stack_size);
return 0;
}
Committed space is not live allocated payload: it includes allocation
headers/footers and free blocks below the break. Freeing an interior block can
make it reusable without moving the break, whereas freeing/coalescing the tail
can reduce committed space. Growth space excludes those reusable interior
blocks, and allocation overhead means it is not a guaranteed maximum malloc
request. heap_committed + heap_growth == heap_capacity.
The API reads the existing linker/runtime symbols in the current address space.
It works for ordinary COM programs and loader-initialized libraries; it does not
assume a fixed TPA ceiling or bank address. The snapshot structure stays local
to its caller's bank, not in the cross-bank contract. Programs that only inspect
memory do not pull in the allocator; unused meminfo code is stripped.
The tmeminfo target regression checks snapshot bounds, stack reserve, errno,
allocation growth, interior free-block reuse, and tail trimming in both compiler
pipelines. The variable-stack Python test reuses that source for 256-, 512-, and
2,048-byte reserves, so the runtime source-coverage audit sees the same calls.
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.