[Home]
[Search]
[Contents]
Compiling Code
The Digital Mars compilers run under Win32 as both a GUI application (the IDDE)
and as a console application. Code can be compiled and executables built for
32 bit Windows, 32 bit Windows console, 16 bit Windows, 16 bit DOS, and 32
bit DOS (with the addition of the X32
dos extender).
This chapter describes how the compiler works by default and how
to customize the compiler's output. It covers both 16-bit and 32-bit
compilations.
For more information about:
What's in This Chapter
A Simple Example
To illustrate compiling the classic hello world! program using the console
compiler, create a text file hello.c with the contents:
#include <stdio.h>
void main()
{
printf("hello world!\n");
}
Compile and link it with the following command:
sc hello
which will compile hello.c to create the object file
hello.obj which is linked to generate the executable
hello.exe. To run the executable type:
hello
which will print:
hello world!
How Digital Mars C and C++ Compiles Code
Compiler options customizes the way Digital Mars C and C++
compile programs. Specify compiler options via the Build
page in the IDDE's Project Settings dialog box or by passing
options as command line arguments to the
SC
control program. All compiler options are optional; the
default settings are oriented towards generating a complete compiled
program.
The following sections explain how the compiler handles various
aspects of compilation and how to control it.
Overview of the compilation process
Digital Mars C++ is an extremely fast one-pass compilation system with
highly effective register utilization. An optional global optimization
facility provides tight and efficient object code. Run the compiler
from within the IDDE or more traditionally using SC from the console
command line.
The compiler executables can compile either C or C++ files. The preprocessor
is integrated in; but for specialized applications a separate standalone
preprocessor is also included.
As it runs, the compiler reads the source files, executes all
preprocessor functions, checks for any syntax errors, optimizes the
program, generates machine code, and outputs an object (.obj) file
suitable for input to the linker.
Startup code
Programs compiled with Digital Mars C++ call one of several startup
functions before passing control to a program's main() routine.
(The startup function called depends on what kind of application is
being built, as described in __acrtused.)
This startup function performs any required initializations
such as initializing data and running the static constructors.
There are several files that contain startup functions (all with
different names) in several subdirectories of the Digital Mars C++
\sc\src directory.
Specifying compiler output
By default, Digital Mars C++ compiles and links the source files
given on the command line
and generates an executable file whose name is the same as
the name of the first source file with the extension replaced with .exe.
To generate different files or additional
files use the SC options described below or via corresponding
options in the Compiler Output subpage on the Build page of the
IDDE's Project Settings dialog box.
-cod Generate assembly language output file
-e Show preprocessor output
-EC Leave comments in preprocessed output
-EL Do not emit #line directives
-l[listfile] Generate source listing file
-ofilename Specify output .obj or .exe filename
-v[0|1|2] verbose compile
Support for C language features
-A Enforce strict compliance with ANSI C
-J Treat chars as unsigned
-Jm Relax type-checking
-Ju char and unsigned char are the same type (unsigned char)
-p Turn off function auto-prototyping
-r Require strict function prototyping
-u Suppress non-ANSI predefined macros
Support for C++ language features
Digital Mars C++ supports run time type identification, exception
handling, and overloading of new[] and delete[]. These new C++
language features are not enabled by default; to enable them, you
need to specify compiler options as described below.
-A Enforce strict compliance with ANSI C++
-Aa Enable new[] and delete[] overloading
-Ar Enable run time type identification
-Ae Enable exception handling
-cpp Compile all source files as C++
-J Treat chars as unsigned
-Ju char and unsigned char are the same type (unsigned char)
-u Suppress non-ANSI predefined macros
Suppressing warnings and errors
By default, the Digital Mars C and C++ compilers generate
warnings about certain constructs that, while legal, are likely
mistakes. Warnings can be disabled individually or as a group.
Warnings can also be treated as errors.
In the IDDE, these are set via the Compiler Output
subpage on the Build page of the IDDE's Project Settings dialog
box.
See "Warnings and associated warning numbers" for messages.
-w Suppress all warning messages
-w[number] Suppressing warning message number
-w- Show all compiler warnings
-wx Treat warnings as errors
-x Turn off compilation error limit
Dynamic profiling
-gt and -Nc Turn on dynamic profiling.
See Trace Dynamic Profiling.
Using precompiled headers
If your program uses a large header file or numerous small headers,
the compiler spends considerable time compiling the same code
over and over again. To save compile time, you can precompile a
header file; the compiler can load a precompiled header faster than
it can a text header file. Digital Mars C++ includes a precompiled
header only once in a file, regardless of how many #include
statements for it appear.
Information about compiler options for using precompiled headers
follows this section.
How the compiler precompiles a header
To precompile a header, the compiler writes out the header file's
symbol table. It saves only the global symbol table, including any
macros the header defines. A header you plan to precompile should,
therefore, contain only declarations and no definitions.
For example, the following declarations are appropriate for use in
precompiled headers:
extern int abc;
int foo(int x);
inline void g(x) { foo(x); } // inline function definition
#define MACRO(x) asdf(x)
These will not work:
int def = 3; // a definition
void g(x) { foo(x); } // function definition
To use a precompiled header in your program, follow these steps:
- Create a directory for your program's files.
- #include the header file in your program.
- Edit your program's makefile.
You should create a directory specifically for your program that
includes its source files and makefile. When compiling,
this directory is the current directory, and the compiler
automatically writes the precompiled headers there. A program
should have its own directory because precompiled headers are
highly specialized, and it is unlikely that a precompiled header for
one program can work with another.
#include the precompiled header in the source as you
normally would. For example, to use a precompiled version of
stdio.h in a program, write:
#include <stdio.h>
main()
{
printf("hello world\n");
}
Note:
#include directives for precompiled header file(s)
should be the first line(s) in a source file other than
comments. If other code precedes the #include
directive for a precompiled header, you need to
ensure that your code does not affect compilation of
the header file.
The program's makefile must precompile the header file and include
the precompiled header in the list of dependencies for all the source
files that #include it. For example, this is the makefile for the
hello world program:
stdio.sym : stdio.h makefile
sc -HF stdio.h
hello.exe : hello.c stdio.sym
sc -H hello
The first two lines are for the precompiled header. The name of the
precompiled header is the same as the text header, except it ends in
.sym instead of .h. The SC -HF command precompiles the header
file, creates a .sym file and places it in the current directory. The
precompiled header depends not only on the text header file, but
also on the program's makefile. This is because changes in the
makefile, such as using a different memory model or target CPU,
change how Digital Mars C++ precompiles the header.
The last two lines above are for the program's source file. The
executable file is dependent not only on its source file, but also on
the precompiled header. The -H option tells the compiler that when
it looks for a header file, it should first look in the current directory
for .sym files.
Using a project precompiled header
If many of your source files #include the same files, you might
want to create a project precompiled header that #includes
several text header files.
For example, if the files in your project all #include stdio.h,
string.h and myglobals.h, you could create a project header
file project.h that contains just these lines:
#include <stdio.h>
#include <string.h>
#include "myglobals.h"
Precompile project.h with this command:
sc -HF project. h
Then compile them with the -H and -HI options:
sc file1 file2 file3 -HIproject.h -H
Using a RAM disk with a precompiled header
To make a precompiled header file even faster,
place it on a RAM disk. To let the compiler know where the
precompiled file is, make some changes in the
makefile. First, precompile the header file with the -HF option to
specify where to place it. Then, compile the source files with the
-HD option to specify where to look for it.
For example, if your RAM disk is drive e, the makefile for the hello
world program looks like this:
TMP= e:
$(TMP)stdio.sym : stdio.h makefile
sc -HF$(TMP) stdio stdio.h
hello.exe : hello.c $(TMP)stdio.sym
sc -HD$(TMP) hello
or, with automatic precompiled headers:
TMP=e:
hello.exe : hello.c
sc -HD$(TMP) -HX hello
Avoiding problems with precompiled headers
To make the best use of precompiled headers, follow these rules:
- Do not write declarations that cross the boundaries of
#include files. Every #include file should be self-contained.
- Do not write extern "C" constructs that start in one file
and end in another.
- Do not depend on #include files being included more
than once.
- Put only declarations, not code or data definitions, in
#include files.
Precompiled header options
Corresponding IDDE options are in the
Header Files subpage on the Build page of the IDDE's Project
Settings dialog box.
-H Use precompiled headers
-H- Do not use precompiled headers (default).
-HC Do not cache precompiled headers in memory
-HDdirectory Read/write precompiled headers in directory
-HF[filename[.sym]] generate precompiled header to filename.sym.
-HMaddress Set address for memory mapped precompiled headerfiles
Note:
If the header file has changed since the last time
you precompiled it, the compiler uses the older
precompiled header file. Updating the precompiled header
file should be done by the makefile.
-HX Use "automatic" precompiled header
Using -HX improves compile times for
most programs. Instances where this might not be the case include:
- When the file being compiled causes scph.sym to be
regenerated but that contains a subset of the #include
files as do other files in the program.
- When #include files are nested in #if or extern "C"
{ } blocks. Here, you should put the extern "C"
statements in the #include files themselves.
Including header files
You can tell the compiler to include header files and where to look
for them by using the options below.
-HIfilename include filename
-HO Include header files only once
-Ipath Set search path for include files
-HS Search for include files only in -Ipath
directories
Choosing a memory model
Memory model choices trade off between meeting
minimum system requirements, maximizing code efficiency,
and accessing as much available memory as possible.
For more information, see Chapter 7, "Choosing a Memory Model."
To specify the memory model, use the SC options
described below or use the Memory Models subpage on the Build
page of the IDDE's Project Settings dialog box.
For more information on memory allocation, see Chapter 5, "Using
Assembly Language Functions."
-m[tsmclvfnrpxz][do][w][u] Select memory model
Specifies the memory model to compile for. The default for SC is the
Win32 model (-mn).
Code Data Model
size size
t 64Kb total Tiny (.COM program)
s 64Kb 64Kb Small (default) 64KB 64KB
m 1MB 64KB Medium
c 64KB 1MB Compact
l 1MB 1MB Large
x 4GB 4GB DOSX 32-Bit Extended:
requires DOSX DOS Extender
p 4GB 4GB Phar Lap 32-Bit Extended:
requires Phar Lap 386|DOS Extender SDK
(5.0 or newer).
n 4GB 4GB Win32 (Win32s, Windows 95, 98, NT, 2000)
f 4GB 4GB OS/2 2.0 (Flat)
Note:
For Windows NT, it is imperative that SS==DS==ES
at all times. Otherwise the NT API functions will
crash. (CS need not be equal to SS, FS is reserved
for thread-related information, and GS is available
for general use.) Thus, SS==DS==ES for the NT
(-mn) memory model.
Here are some example combinations of memory model specifiers:
-ms Small model with SS == DS
-msw Small model SS != DS
-mc Compact model
-mt .com file
-mluw Large model, SS != DS, Reload DS
The program stack
In all memory models, the stack grows downward toward statically
allocated data. If the stack grows larger than its allocated size,
statically allocated data are destroyed, and the program performs
unpredictably. To check if the stack grows past the allocated limit,
use the special function called _chkstack.
Call _chkstack from functions that potentially use large amounts
of stack space, such as recursive functions. If the stack exceeds the
allocated size, _chkstack aborts the program with the message:
Stack overflow
Use the -s compiler option (see below) to generate stack-overflow
checking code on entry to every function, although this increases
program size and reduces speed. Use stack-overflow checking if you
suspect stack-overflow errors (a symptom is a mysterious crash).
Controlling stack size for DOS programs
You can control a DOS program's stack size in the following ways:
For Win16 and Win32 compilations, use the linker's
/STACK switch to set the stack size.
For a DOS program, do not use the linker /STACK
parameter to set the stack size; use one of the
methods described above.
The default stack size is 8192 bytes for 16-bit compilations and 16KB
for 32-bit compilations.
-s Check for stack overflow
Aligning data objects
In 16-bit compilations, the compiler align data objects on
16-bit word boundaries. This improves the execution speed of code
running on CPUs with a 16-bit data bus (like the 80286). The default
alignment for 32-bit compilations is on 32-bit boundaries.
The default alignment of structure members depends on the memory
model used for the compilation. You can change the alignment
within structures via the -a compiler option (see the section "-a
Specify structure alignment" later in this chapter), so that structure
members are aligned on different boundaries. This option is useful
for defining a struct that maps onto a hardware device or a
predefined data element.
You can control alignment only within structures; the compiler still
aligns all other data objects on word or dword boundaries.
Struct member alignment can also be specified with the #pragma pack
preprocessor directive. Refer to Chapter 3, "Digital Mars C++ Language
Implementation" for more information.
The C++ compiler does not generate structs with a size of 0 bytes if
there are no nonstatic data members; the minimum size of a struct is
1 byte. This prevents new() from returning 0 when it tries to allocate
an instance of a struct.
Warning:
Each file referencing a structure must be compiled
with the same type of alignment. If two files
compiled with different alignment reference the
same structure, any erroneous results could be hard
to debug.
-a Specify structure member alignment
You can set this option from the IDDE in the Compiler
subpage on the Build page of the IDDE's Project Settings dialog
box.
Compiling for debugging
Programs compiled with Digital Mars C and C++ can be debugged
using many third-party debuggers, as well as the Digital Mars C and
C++ debuggers.
There are two basic types of debugger that can be used with
Digital Mars C and C++ programs: fully symbolic debuggers (those that
work with symbolic information, like the Digital Mars debuggers or
Microsoft Corporation's CodeView), and partially symbolic
debuggers (those that require only source code line numbers and
map file information, such as Microsoft's Symdeb).
Compatibility with third-party C debuggers
Fully symbolic debuggers that are compatible with the data format
used by Microsoft CodeView should give acceptable results when
used to debug C programs compiled with Digital Mars C++, provided
they can handle both C and Pascal function calling conventions.
Some information on local variables may not be available due to the
automatic register assignments carried out by Digital Mars compilers.
(Digital Mars C and C++ make intelligent use of registers within
functions, automatically assigning frequently used variables to
registers. This register assignment uses any available register and not
simply SI and DI.) Some debuggers cannot handle variables in
registers other than SI and DI and consequently cannot display or
track them.
The Digital Mars C and C++ debuggers can display, modify, and track
all the local variables within a function.
Compatibility with third-party C++ debuggers
Although some other third-party C
debuggers can partially debug C++ programs, they cannot obtain or
handle all the information needed to fully debug C++ code. The
main difficulties are these:
- C++ function names are displayed by the debugger in
their mangled form, as used internally by the compiler.
- Expanding a derived class shows only those variables
added in the derived class. Base-class variables cannot be
seen.
- Pointers to a base class always are shown as such, even if
they really point to a derived class.
- Inline functions can cause some debuggers severe
problems and must be avoided.
- C debuggers have no facilities for examining class
definitions or hierarchies.
To fully overcome these problems, you must use a C++ debugger.
The Digital Mars C++ debugger provides full debugging facilities for
C++ programmers. It lets class definitions be fully expanded, C++
scope rules be understood, pointer conversions be done
automatically, overloaded operators be presented correctly, and
function prototypes be examined.
Overview of compiler debugging options
If the compiler always generates full debugging information for all
variables, then the object files would be very large, and the linker would
have to remove a great deal of redundant information. You can
control the level of debugging information the compiler provides
with the -g option (see the section "-g Generate debugging
information" later in this chapter). The compiler can produce five
levels of debugging information:
- Full symbolic debugging and line number information
(-g option) for all variables used in a module. Programs
can be debugged at the source level with full debugging
information.
- Symbolic information only (-gs option). This provides all
the symbolic information but does not generate any line
number information. This level conserves space and
allows the debugger to work on larger programs but
prevents debugging at the source level; programs must be
debugged at the object code level.
- Symbolic information for global structs and typedefs,
whether they are referenced or not, as well as for all
variables referenced (-gh option).
- Symbolic information for each class that the generated
code references, as well as for all variables referenced.
(-gf option).
- Line numbers only (-gl option). This option is mainly for
use with partially symbolic debuggers. It emits no
symbolic information, except what is normally placed in
the linker map file. This level provides full line number
information. This allows debugging at the source code
level, but only global symbols are available; local and
static symbols cannot be observed.
Compiling with any level of debugging information significantly
increases the size of the object file. The size of the executable file
also increases, often doubling.
Debugging options
-C Prevent inline expansion of C++ functions
-g Generate debug info for types of referenced variables
-g4 Generate Codeview 4 format debug info. This is the default.
-gf Generate debug info for full class hierarchy for any referenced
classes.
-gg Make static functions global
-gh Generate debug info for all global structs and typedefs
-gl Generate line number info only
-gp Generate pointer validation code
-gs Generate symbol info only (do not generate line number info)
-gt Generate prolog/epilog for use with the dynamic profiler.
-S Always generate stack frame
Linking for debugging
When compiling for debugging, the compiler automatically passes
the correct information to the linker. If you use a separate link step,
you need to pass the appropriate options to the linker yourself.
These are the options for Digital Mars OPTLINK or Microsoft
LINK for all levels of debugging information:
- /m (create map file)
- /co (link for CodeView)
- To use Microsoft LINK, specify the -Llinker compiler
option. linker is which linker to run.
Only Microsoft LINK 5.1 or later versions correctly
handle the symbolic information.
Note:
When creating debugging information, OPTLINK
uses CodeView Version 4 format if any object
module in the link step contains CV4 records. In this
case, it throws away any CV3 records it encounters.
OPTLINK creates an executable with CV3
debugging format only if every debug record is in
CV3 format. To retain CV3 records in objects, run
OPTLINK with the /CO:3 or /NOCVPACK options,
and then run Microsoft's CVPACK.
See the User's Guide and Reference for more information on using
the Digital Mars debuggers.
Notes on debugging with CodeView
Unlike the Digital Mars C++ debugger, which is statement oriented, the
Microsoft CodeView debugger is a line oriented source debugger. At
the source code level, the smallest unit of code CodeView can
handle is a line, not a statement. To use CodeView most effectively
with Digital Mars C++, follow these rules:
- CodeView cannot handle inline functions.
- Make each statement in the source code occupy a
separate line. Also, CodeView does not break down
complex macros, so if you suspect a macro of containing
a bug, write the macro as a regular source statement.
- Compile source modules with the -g option and link with
the /co (CodeView) and /noi (do-not-ignore case)
options.
- An apparent limitation causes CodeView for Windows to
halt unexpectedly if you attempt to debug executables
that contain large tables, such as applications built with
MFC and compiled with Digital Mars C++. One workaround
is to find the table that exceeds the maximum size,
rebuild the objects that contribute to it without debugging
information, and then re-link. Or, relink the with
OPTLINK using the /NODEBUG and /DEBUG options (no
need to recompile).
Refer to Microsoft's documentation for information on CodeView.
Debugging aids in the run-time library
There are a number of features in the run-time library that help
detect common run-time errors:
- If a NULL pointer is passed to printf where a pointer to
a string is expected, printf uses the string "null"
instead. For example:
printf("String '%s'\n", NULL);
prints:
String '(null)'
- If a NULL near pointer is de-referenced, the string that is
at location 0 in the data segment is "ERROR: NULL
pointer." For example:
char buf[100];
strcpy(buf, NULL);
printf("String '%s'\n", buf);
prints (for code compiled with the Tiny, Small, and
Medium models):
String 'ERROR: NULL pointer'
If you optimize the code you are debugging, remember that
optimization moves variables and sections of code and creates only
one scope per function. Avoid using the same variable names in
nested scopes.
Specifying linker and assembler options
The compiler options described below let you run third party linkers
and assemblers on the SC command line, or pass options to the
linker or assembler.
-L Using non-Digital Mars linker.
-Llinker Use linker linker instead of OPTLINK.
-L/switch Pass /switch to linker.
-Masm Use assembler asm instead of MASM.
-M/switch Pass /switch to assembler.
Controlling segmentation
When writing large 16 bit Windows programs, you sometimes need to
place functions that frequently call each other into the same
segment. This optimization helps speed execution. This section
explains how to segment your code. Use these methods
independently or in any combination.
- Use the -NTcsname compiler option, which sets the
code segment for a file to be csname.
- Use the -NS compiler option, which creates a new code
segment each time the compiler encounters a global far
function. The name of the segment is the name of the
function, with _TEXT appended.
- For Compact and Large model compilations, use the -GT
compiler option, which places arrays larger than a
specified number of bytes in far data segments instead of
DGROUP.
- Use the #pragma:
#pragma SC cseg csname
which places what follows in the segment csname. The
-NS option overrides the #pragma for global far
functions.
Preventing data segment overflow
Many large programs run out of space in the default data segment
DGROUP. The solution is to move some data out of DGROUP into
other segments. Here are some methods you can use:
- Compile with the -R option, which causes switch
statement tables to be placed in the code segment rather
than in DGROUP.
- Declare literal strings you want to place in the code
segment with the type modifier __cs.
- Compile with the -Ns option, which places strings that
are part of expressions into the code segment rather than
in DGROUP.
- Declare statically allocated data as far; for example:
int __far array[100];
For information on declaring data as far and placing data in the code
segment, see Chapter 7, "Choosing a Memory Model."
Data declared as far should be data that is relatively large and is
accessed relatively infrequently, as more overhead is required to
access those data.
If another module should reference far static data, declare it as far, as
in:
extern int __far array[];
Otherwise, the compiler assumes that the data is in the default data
segment DGROUP. Digital Mars C++ differs from some other compilers
in this respect— extern data is not assumed to be in DGROUP.
Although our implementation involves more work on the program-
mer's part (putting __far in the right places), the control this offers
makes it very worthwhile for high-performance applications. Also,
most applications need less than 64KB of static data, and so need not
pay a penalty for assuming that extern data is not accessible via
DS.
Virtual function tables (vtbls)
The compiler generates a table of pointers to functions for each class
that has virtual functions or is derived from a class with virtual
functions. In the small data models (Tiny, Small, and Medium), it
creates these tables in DGROUP by default. For the Compact and
Large data models, it writes these tables to the code segment by
default.
To further minimize use of code space in Compact and Large model
programs, place vtbls in far data segments with -NV.
Segmentation options
Use the options described below to control how the compiler
segments your program.
-NS Create new code segments for far functions
-Ns Put strings in expressions into code segment
-NTname Set code segment name to name.
-NV Put virtual function tables in far data segments.
-R Put switch tables in code segment
-GTnnnn Put large arrays in far data segments.
Code generation options
The options described below control how the compiler generates
code.
-0, -2, -3, -4, -5, -6 Specify CPU instruction set
-c Compile only, do not link executable.
-C Disable inline function expansion.
-D[macro[=text]] Define macro
-Nc Perform function level linking
-NF ignore extended keywords
-NL Do not embed default library record
<-b>-o Optimize code for speed
<-b>-o+space Optimize code for space
-P Default to pascal linkage
Compiling for Windows
See Windows Prolog/Epilog Code Generation.
Templates
-XIspec Create instance of template
-XD instantiate template for any functions declared but not defined
-XN no template function instantiation
Internationalization Options
-j[0|1|2] Recognize multibyte Asian language characters in the source