Building and linking¶
This page covers the path from a .c file to a runnable CP/M .COM program and
the compiler options that most affect the result. You typically do this in your
own project directory — DCC C Compiler and ntvcm are general-purpose tools for building
CP/M / Z80 C apps anywhere, not just inside the DCC C Compiler repo. As long as the tools
are on your PATH (see Setting up the toolchain), the
commands below work from any folder that holds your .c sources.
Build with dccmake¶
dccmake is the native build driver. With the tools on PATH and the DCC
headers and runtime configured for your project, it builds one or more sources:
dccmake main.c dcc-output=MAIN
dccmake main.c helper.c dcc-output=MAIN dcc-stack-check=true
ntvcm build/MAIN.COM
The first source is the application translation unit; subsequent sources are
compiled with -module. Outputs go to build/ by default. Source basenames
and the output name must be CP/M 8.3-compatible (at most eight characters before
the extension, and three after it). Host directory paths can be longer.
For repeatable builds, put settings in dccmake.txt in the working directory:
dcc-input=main.c,helper.c
dcc-output=MAIN
dcc-stack-bytes=1024
dcc-stack-check=true
dcc-peep=true
Run dccmake without source arguments to use that configuration. For projects
outside the checkout, also configure dcc-include-directory and
dcc-runtime as described in the dccmake reference.
That reference covers tool overrides, configuration precedence, debug modes,
and the optional emulated assembler/linker paths.
The native linker normally uses /P:100, CP/M's standard .COM load address.
For fixed-address, overlay, or relocating system programs,
l80c also supports LINK-80-style
nonstandard origins and generates the required CP/M entry jump/bootstrap. Its
utility reference covers the CLI options, origin layouts, and limitations.
The manual pipeline (click to expand)
For manual builds or custom build systems, the full pipeline for foo.c is
shown below. Every build tool runs natively on the host; only the resulting
FOO.COM needs CP/M or an emulator.
Compile and optionally optimize:
dcc -I C:\path\to\dcc -stack 512 foo.c -o FOO.MAC
dccpeep FOO.MAC _PEEPOUT.MAC
Move-Item -Force _PEEPOUT.MAC FOO.MAC
Strip unreachable application blocks, then assemble:
dccrtlstrip --strip-apps FOO.MAC
m80c "=FOO.MAC" /X /O /Z /L
Copy and trim the runtime, then assemble and link:
Copy-Item C:\path\to\dcc\DCCRTL.MAC DCCRTL.MAC
dccrtlstrip -r DCCRTL.MAC -o RTLMIN.MAC FOO.MAC
m80c "=RTLMIN.MAC" /X /O /Z
l80c "/P:100,RTLMIN,FOO,FOO/N/E/Y"
Compile and optionally optimize:
dcc -I /path/to/dcc -stack 512 foo.c -o FOO.MAC
dccpeep FOO.MAC _PEEPOUT.MAC
mv _PEEPOUT.MAC FOO.MAC
Strip and assemble the application, trim and assemble the runtime, then link:
dccrtlstrip --strip-apps FOO.MAC
m80c "=FOO.MAC" /X /O /Z /L
cp /path/to/dcc/DCCRTL.MAC DCCRTL.MAC
dccrtlstrip -r DCCRTL.MAC -o RTLMIN.MAC FOO.MAC
m80c "=RTLMIN.MAC" /X /O /Z
l80c "/P:100,RTLMIN,FOO,FOO/N/E/Y"
Replace /path/to/dcc (or C:\path\to\dcc) with the checkout
directory containing the standard headers and DCCRTL.MAC. Native
m80c accepts normal host
line endings; no CP/M text conversion is needed.
To cross-check compatibility with the original Microsoft tools, use
dccmake foo.c dcc-output=FOO dcc-use-emulated-m80=true dcc-use-emulated-l80=true
with the project's headers, runtime, and emulated tools configured. That optional path stages
M80.COM/L80.COM, converts text to CP/M CRLF, and runs them under
ntvcm.
The compiler invocation¶
dcc [options] input.c [-o output.mac]
Common options:
| Option | Meaning |
|---|---|
-o file |
Write m80c-compatible assembly to file; default is out.mac, - is stdout. |
-c, -module |
Emit a separately compilable module, not a final program translation unit. |
-f, -ffloatio |
Force %f support on every printf-family call. |
-fl, -flongio |
Force 32-bit long formats on every printf-family call. |
-fno-floatio, -fno-longio |
Force the corresponding format paths off, overriding automatic detection. |
-fhexio, -fno-hexio |
Force hexadecimal output support on or off. |
-foctio, -fno-octio |
Force octal output support on or off. |
-g |
Emit full source-debug metadata with conservative code generation. |
-gline |
Emit optimized debug metadata while retaining release code generation. |
-fstack-check |
Guard function prologues and VLA allocations against stack reserve overflow. |
-fno-narrow |
Disable integer-array, scalar, and loop-counter byte-narrowing optimizations. |
-s bytes, -stack bytes, --stack bytes |
Reserve stack bytes; default is 512. |
-s=bytes, -stack=bytes, --stack=bytes |
Equivalent attached forms for the stack size. |
-I dir, -Idir |
Add an include search directory. |
-D name[=value], -Dname[=value] |
Predefine a macro. |
-U name, -Uname |
Undefine a preprocessor macro. |
-v, --version |
Print the compiler version and exit. |
-h, --help |
Print compiler help and exit. |
There is no -std=c89, -std=c99, or -std=c11 mode selector: the
documented language subset is always in effect. Unknown
compiler options are currently ignored, so an accepted command line does not
prove that a familiar desktop-compiler flag took effect.
Options that affect the runtime¶
For each printf-family call with a compile-time literal format, dcc detects
%f, long, hexadecimal, and octal conversions and selects the smallest matching
runtime entry automatically. Calls with non-literal formats conservatively
include all of those conversion paths.
-f/-ffloatio— force floating-point%fsupport on everyprintf-family call, including calls whose literal format does not use it. This is normally useful only when forcing a whole-program policy; non-literal formats already use a conservative fallback.-fl/-flongio— similarly force 32-bitlongformats (%ld,%lu,%lx,%lX,%ls) on everyprintf-family call.-fno-floatio/-fno-longio— force the corresponding support off, even for a literal that uses it or a non-literal fallback. Use these size-oriented overrides only when no affected conversion can reach any call. None of these options adds floating-pointscanfinput.-s/-stack/--stack— reserve stack space (default 512; accepted range 0..32767). The heap used bymalloclives between the end of BSS and the bottom of the stack, so growing the stack shrinks the heap and vice versa. By default there are no runtime checks that stop the stack from smashing the heap.-fstack-check— opt in to a lightweight stack-overflow guard. The DCC C Compiler emits a shortcall __stchkin each function prologue (after the frame is set up) that compares the live stack pointer against the heap ceiling. If the stack has grown into the heap, the program prints?stack overflowand exits with return code0FFhinstead of silently corrupting memory. The guard costs a few bytes and one call per function, so it is off by default; turn it on while developing or for deeply recursive code. Thestacksizeutility (below) uses this guard to measure the minimum-stackreserve an app needs. This option sets the initial state for the translation unit; source can then use#pragma stack_check(on)/#pragma stack_check(off)to control guard emission in source order.-Dname[=value]— predefine a macro._DCC_=1is always defined.
Measuring the stack an app needs¶
The repo ships a stacksize utility that builds your app with -fstack-check
forced on and sweeps the -stack reserve upward until it runs without tripping
the guard, then prints the minimum and a recommended value with headroom. Run it
against an app/test name (and pass any program arguments after --):
rem simple app
scripts\stacksize.bat triangle
rem app that needs a data-file argument
scripts\stacksize.bat cobint -- e.cob
# simple app
scripts/stacksize.sh triangle
# app that needs a data-file argument
scripts/stacksize.sh cobint -- e.cob
# simple app
scripts/stacksize.sh triangle
# app that needs a data-file argument
scripts/stacksize.sh cobint -- e.cob
# simple app
scripts/stacksize.sh triangle
# app that needs a data-file argument
scripts/stacksize.sh cobint -- e.cob
rem simple app
scripts\stacksize.bat triangle
rem app that needs a data-file argument
scripts\stacksize.bat cobint -- e.cob
Both honour the same START / STEP / MAX / MODE / EMU environment
variables; see scripts/README.md
for the full reference.
Including headers¶
Include the standard headers as usual:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
DCC searches the name as given (including the working directory), then the
including file's directory, then each -I directory in command-line order.
The bundled headers live beside DCCRTL.MAC at the root of the source checkout.
For an external dccmake project, set dcc-include-directory=/path/to/dcc
and dcc-runtime=/path/to/dcc/DCCRTL.MAC, replacing /path/to/dcc with that
checkout's path. When invoking the compiler directly, use -I /path/to/dcc.
Missing system headers are silently ignored
An unresolved <header.h> include is skipped; an unresolved
"header.h" include is an error. Successful compilation therefore does
not prove that a system header was found. Missing prototypes can produce
wrong calls, especially for pointer, long, or float results. Use DCC's
headers, not your host compiler's standard-library headers.
Multi-module symbol names¶
m80c and
l80c keep only the first 6
characters of a public (external) symbol.
DCC C Compiler emits each external C identifier as _ followed by the name, so the leading
underscore consumes one of those six characters. The practical rule for any
program built from more than one .c file is:
Every non-
staticfunction and non-staticfile-scope variable must be unique within its first 5 characters across all linked modules.
Names that only differ after the fifth character collapse to the same public
symbol. For example i_idxins, i_idxbld, and i_idxlookup all become
_I_IDX and are indistinguishable to the linker.
Anything used in only one translation unit should be declared static. A
static symbol has internal linkage, so DCC C Compiler gives it a private, generated
assembler name and the 6-character rule does not apply to it.
How a collision shows up¶
-
Within one file, DCC C Compiler catches it at compile time and stops with an error naming both symbols, for example:
global names 'i_idxins' and 'i_idxbld' are not distinguishable in M80's 6 significant character public symbols (both become '_I_IDX'); rename one
- Across different files, DCC C Compiler cannot see the clash.
l80creports a multiply defined global rather than silently binding a call to the wrong definition.
Fixing collisions¶
- Rename the offending identifiers so they differ within the first 5
characters (put the distinguishing letters early:
ixins,ixbld,ixlookrather than a sharedi_idx…prefix). - Or make single-file helpers
static.
Struct, union, and enum tags, typedef names, struct members, macros, enum
constants, and local variables never become public symbols, so they are exempt.
Detecting collisions¶
After a build, scan the emitted .MAC modules for external names that share a
6-character prefix:
grep -rhiE '^[[:space:]]*public ' build/*.MAC \
| awk '{print $2}' | sort -u \
| awk '{k=toupper(substr($0,1,6));
if (seen[k]) print "COLLISION " k ": " first[k] " <> " $0;
else { seen[k]=1; first[k]=$0 }}'
Any line printed is a pair you must rename or make static.
Memory layout¶
CP/M loads .COM files in one way. BSS begins immediately after the loaded
image. The CP/M loader itself sets SP to its own small CCP stack, not to the
top of free memory; DCCRTL's startup code is what then sets SP to the
highest free byte before your program runs. The heap grows on demand between
the end of BSS and the bottom of the stack. Because there is no guard between
them by default, size the stack deliberately with -stack for programs with
deep recursion or large frames — or build with -fstack-check (above) to turn
an overflow into a clean ?stack overflow exit.