User Manual Miracle C Compiler version 1.7 1 December 1996 Copyright 1989-96 bts The Miracle C Compiler runs on a 386 PC (or better) under MS-DOS, accepting a dialect of the C language and generating object code suitable for Microsoft or compatible linker. All of traditional (Kernighan & Ritchie) C syntax is implemented, including record (struct/union) and enumerated data types, int, long and floating point data types, user type definition, bit fields in structs, initializers for all data types. Both traditional and new (ANSI) function declaration is supported. There is a comprehensive library of functions. Table of Contents REGISTRATION FOR SOURCE CODE 4 DISCLAIMER 4 USING THE COMPILER 4 FLOATING POINT SUPPORT 5 ENVIRONMENT VARIABLES 5 COMPILER SWITCHES 6 EXAMPLE PROGRAMS 6 LANGUAGE FEATURES 8 LEXICAL ELEMENTS 8 1. Operators 8 2. Separators 8 3. Identifiers 8 4. Reserved words 8 5. Constants 8 PREPROCESSOR 9 Macro substitution 9 File inclusion 10 Conditional Compilation 11 Other Features 12 FUNCTIONS 12 TYPES 14 Record (Struct/Union) 15 Typedef 15 Type Compatibility 16 DECLARATIONS 16 Initializers 19 EXPRESSIONS 20 STATEMENTS 22 ASSEMBLER PROGRAMMING INTERFACE 23 FUNCTION LIBRARY 24 STARTUP CODE 24 INCLUDE HEADER FILES 24 LIBRARY FUNCTIONS 24 abs, labs 24 calloc 24 close 25 create 26 exit 26 fclose 26 feof 27 fflush 27 fgetc 28 fgets 28 fopen 29 fprintf 29 fputc 30 fputs 30 fread 31 free 31 fscanf 32 fwrite 32 getc 32 getchar 33 gets 33 is-ctype 34 malloc 34 memchr 35 memcmp 35 memcpy, memmove 35 memset 35 open 36 printf 36 putc 37 putchar 37 puts 38 read 38 scanf 39 sprintf 40 sscanf 40 strcat 40 strchr 41 strcmp 41 strcpy 41 strdup 41 strlen 41 strlwr 42 strncat 42 strncmp 42 strncpy 42 strnicmp 42 strpbrk 43 strrchr 43 strrev 43 strspn 43 strupr 43 tolower, toupper 44 ungetc 44 vsprintf 44 write 45 REGISTRATION FOR SOURCE CODE Source code to the compiler itself is supplied to registered users. You may modify the compiler source code as you wish, provided that you acknowledge its origins. Microsoft’s MSVC compiler is required to compile the source code into an executable. PharLap’s 286 DOS-Extender Lite, which is supplied as part of MSVC, is a required step in the process of linking the compiler executable. Registration costs US$10, GBP10 or Can$15, and entitles you both to the source for the compiler itself (excluding preprocessor and libraries, which are supplied as linkable object), and to receive upgrades for one year. Payment should be made out to the author “BT Szocik”. You can register by post or through Compuserve. Register on Compuserve by choosing GO SWREG. The registration-id of Miracle C is 13266. Registration by regular mail is from the developer; BT Szocik 45 Englewood Road London SW12 9PA England By registering you support further development of the compiler. Should you wish to make a direct contribution to a future version (for example by writing a compiler utility, library functions or source code), please write to the developer at the above address. DISCLAIMER The compiler should be regarded as a teaching tool and is distributed on the shareware principle, without any warranty, without even warranty of merchantability or fitness for a particular purpose. No guarantee is made regarding the performance of the compiler, and no responsibility can be taken for use of, or inability to use, the compiler. Source code is provided on registration in support of the compiler’s intended role as a teaching tool. USING THE COMPILER The following files are present in a compressed form in the archive; miracle.doc documentation in MS Word format miracle.txt documentation in text format cc.exe the C Compiler ccl.lib C compiler library mc.bat batch file for compiler and several example programs; example.c hanoi.c cat.c sieve.c maze.c maze slr.c grammar.c Floating point support The compiler requires an 8087 coprocessor (or better), or emulation program, to perform floating point arithmetic. Such a coprocessor is integrated into the 80486 and subsequent CPUs. If your system does not have a numeric coprocessor you should obtain a public domain emulation program such as em87.com to perform floating point computations. Environment Variables The compiler uses the following environment variables; LIB directory containing library ccl.lib (this is only used by a linker) INCLUDE directory containing system include files <*.h> (if unset, defaults to c:\cc\include) LINKER name of linker, eg LINKER=BLINK (if unset, defaults to LINK, the Microsoft linker) Note that a linker is not supplied with the compiler. Use the Microsoft linker supplied with DOS, or a compatible linker. CC output is Microsoft/Intel object module format, as documented in the MS-DOS Encyclopedia. The linker must accept Microsoft object. Before compiling a program set the environment variables to suitable values; C:\> SET LIB=C:\CC C:\> SET INCLUDE=C:\CC\INCLUDE C:\> SET LINKER=LINK Compile one of the example files; C:\MIRACLE> MC EXAMPLE +---------------------------------------------- ---------+ ¦ Extended-DOS Power by ¦ ¦ Phar Lap's 286|DOS-Extender Lite (TM) Version 3.0 ¦ ¦ Copyright 1993 Phar Lap Software Inc. ¦ ¦ Total Memory = 12999 Kb, Available Memory = 2048 Kb ¦ ¦ For more information call Phar Lap at 617- 661-1510 ¦ +---------------------------------------------- ---------+ Miracle C Compiler (r1.7), written by bts. C:\MIRACLE> C:\MIRACLE>link example,example,example,ccl; Microsoft (R) Segmented Executable Linker Version 5.60 Copyright (C) Microsoft Corp 1984-1993. All rights reserved. C:\MIRACLE> This will compile and link `example.c', creating example.obj, example.exe. Compiler switches -j signed char constants -n noisy compile -p disable autoprototyping -r set error limit -s generate source listing (.src) after preprocessing -t allow trigraphs -w no warnings -Dname define macro -Uname undefine macro -Iname add include directory Only one program file may be compiled at one time. To compile and link together several programs, compile them separately with `-c' option, then link them with ccl.lib. If the compiler finds an error (in preprocessing, parsing or other compilation error, eg an undeclared variable) it will print the statement where it occurred and stop if the error limit has been reached. Functions should be declared before being called, although this is not mandatory. If they are not, then auto- prototyping will generate a prototype for a new function returning int. A function definition automatically declares the function if it has not been declared or auto-prototyped earlier. EXAMPLE PROGRAMS A few example programs are included to illustrate Miracle C facilities. example.c hanoi.c cat.c sieve.c maze.c maze slr.c grammar.c example.c shows most of CC's features; structs/unions; initializers for structs and arrays; typedefs; function prototyping; functions with local variables, and scoping; data types; signed and unsigned int, char and long variables, floating point; use of some library functions for file handling cat.c reverse words in a sentence, eg `I am here' to `here am I' hanoi.c non-recursive towers of hanoi (originally from a Programmer's Challenge in Computer Shopper magazine!!) gives the sequence of moves to move all discs from first to third peg via second sieve.c Eratosthenes sieve to find prime numbers by eliminating composites developed from a BYTE C benchmark program maze.c maze solver, finds shortest path through maze from A to B maze eg. C:\> MAZE ' ? += -= *= /= %= <<= >>= &= ^= |= -> ++ -- << >> <= >= == != && || 2. Separators Whitespace characters 9-13, and space character 32 are separators. ( ) [ ] { } , ; : 3. Identifiers A sequence of any number of letters/digits/underscores starting with a letter or underscore character. Identifiers are case-sensitive so name, Name and NAME are different, but external symbols (such as function names) are not case-sensitive. 4. Reserved words auto break case char default do else enum extern for goto if int long register return short signed sizeof static struct switch typedef union unsigned void while 5. Constants A C constant can be an integer, character or string, or floating point constant. integer An integer can be represented as a decimal, octal (base 8) or hexadecimal (base 16) constant. * decimal (digits) * octal 0(digits), eg 0243 * hex '0x' (or 0X) (hex-digits 0-9 a-f A-F), eg 0x4afb A numeric constant can be unsigned (suffixed by U, eg 56U), long (eg 128374L) or floating point. Floating point constants have syntax aaa.bbbEeee where aaa.bbb = mantissa eee = exponent (signed, optional) character A character is 'char' or '\esc-char' where esc-char= nnn octal literal xnn hex literal a alert b backspace f form feed n newline t tab r carriage return v vertical tab ' single quote ? question mark " double quote A character constant may contain more than one byte, on which case bytes are packed into a word, for example int a='xy'; string A string is "string" where the string has printable characters or \escaped- characters. Constant strings are null-terminated, eg sizeof("hello")=6 Preprocessor The preprocessor performs macro substitution, file inclusion and conditional compilation on the source program. Use the `-s' compiler option to see the program text after preprocessing. The preprocessor allows a line to be continued by ending it with a `\' character. Tokens and strings can be split across lines. Preprocessor directives are lines starting with `#'. A line with only '#' is a null directive and is ignored by the preprocessor. Macro substitution The #define preprocessor directive defines a macro, optional parameter list and associated substitution string. A macro can have no parameter list, eg #define WORD_SIZE 16 then every occurrence of WORD_SIZE in the program text is replaced with `16'. The general form of a macro definition allows zero or more parameters; #define (parameters) #define afunc() otherfunc() #define times(x,y) ((x)*(y)) every call to afunc() is replaced with a call to otherfunc(); when `times' is used, actual arguments are substituted for macro parameters in the replacement text, eg times(4,times(5,6)) gives ((4)*(((5)*(6)))) The number of arguments when the macro is used must match the number of parameters when it is declared. A macro definition may be split across more than one line by line continuation with \ #define plus(x,y) \ ((x) + (y)) After macro substitution, the line is scanned again so macros created by the expansion can be recognized and expanded. The following macros are predefined by the preprocessor; MIRACLE defined as 1 use for code specific to Miracle C compiler MSDOS defined as 1 use for code specific to MS-DOS __FILE__ current source file __LINE__ line in current source file __DATE__ today's date, returned as "Mmm dd yyyy" __TIME__ time of compilation, returned as "hh:mm:ss" Redefinition of macros (benign or otherwise) is allowed. No warning or error is given if redefinition occurs. A macro definition can be cleared using `#undef' directive; #undef times File inclusion The `#include' directive allows C source files to be included in the program text. When the end of the included file is reached, compilation continues from the line after the `#include' directive. Include files may be nested; but they should including themselves, directly or indirectly, causes the compiler to crash. Preprocessor include files usually have a .h suffix (header files). There are two forms of #include directive; #include "filename" includes a file in the current directory, #include includes a file from the system include directory, which is given by the INCLUDE environment variable. If INCLUDE is not set, the system include directory defaults to `\cc\include'. The inclusion-file can be specified by a macro; #define MYINCLUDES "my.h" #include MYINCLUDES A complete pathname can be given in the first form of include; #include "\cc\graphics\graf.h" Conditional Compilation The preprocessor allows compilation of sections of code to be conditional on the values of expressions, using the directives `#if' `#ifdef' `#ifndef' `#else' `#endif'. Lines after an unsuccessful conditional compilation directive are discarded until the next conditional compilation directive. A conditionally compiled section of code starts with #if #ifdef or #ifndef and ends with a matching #endif. It may contain #else or #elif directives, and other (nested) conditionally compiled code sections. `#if expr' If the C expression `expr' evaluates to 1, lines are processed until a matching else, elif or endif is found. If it evaluates to 0, following lines of code are discarded until a matching else, elif or endif is found. The expression `expr' must be a constant recognisable to the preprocessor, containing macro, character and number constant values only. `#ifdef name' `#ifndef name' If the macro `name' is defined (ifdef) or undefined (ifndef), lines are processed until a matching else, elif or endif is found. Otherwise, following lines of code are discarded until a matching else, elif or endif is found. `#else' The else directive follows an if, ifdef or ifndef, or can follow #elif directives. Lines of code following #else are processed only if preceeding lines were not processed. `#elif' This is a switch/case construct for conditional compilation; #if expr1 ... #elif expr2 ... #elif expr3 (etc) #else ... #endif `#endif' terminates a conditionally compiled code section. Other Features The #error directive aborts compilation with an error message; #ifdef x_once #error Illegal include recursion #endif #define x_once detects a program's attempt to include itself; implement "once-only" inclusion of header files similarly. Stringification converts a token into a string, and is useful when debugging a program; #define fatal(tok1,tok2) printf("bad tokens: %s;%s;",#tok1,#tok2) Concatenation of adjacent strings is supported by Miracle C compiler, so #define fatal(tok1,tok2) printf("bad tokens: " #tok1 ";" #tok2) is allowed. Token merging using the ## operator creates a single token from two or more tokens in a macro definition; #define line(i) line ## i then line(1) == line(2) becomes line1 == line2 Functions Miracle C supports a both the traditional syntax for function definition, and a syntax similar to ANSI C for function prototyping. Functions can be declared and prototyped before use. The traditional way of introducing functions, eg. int main(argc,argv) int argc; char **argv; is supported. Traditionally, functions weren't declared before use or definition; CC allows functions to be declared and prototyped before use, zero times or exactly once, or a parse error results. Note that 'parse errors' occur when the current symbol is not one of the expected legal symbols, hence a function redeclaration may generate a parse error. CC's function declaration syntax is similar to ANSI, eg void afunc(int one, char *b); but the ANSI syntax void afunc(int, char *); is not supported (yet). The number and type of parameters, and return value, in a function definition must match exactly that of the function declaration. Miracle CC supports declaration of functions with a variable number of parameters, eg int printf(char *fmt, ...); but their definition is not supported (yet). Miracle C has the concept of `function types' which are type1 x .. x typen x varargs -> typer for a function taking n parameters of type (type1, ..., typen) and returning a result of type typer, with a variable number of arguments if varargs set. A pointer to a function is assigned a function type, which should match the type of a function assigned to it; for example, void (*fptr)(); int printf(char *fmt, ...); (fptr=&printf,*fptr)("hello"); will fail with the error message 158: wrong # args in function call '(fptr=&printf,*fptr)("hello")' Functions may return signed or unsigned int, char or long values, but not struct or union. They may return pointers to any item (including struct), or a user defined type, as long as that type isn't a struct. Nor can a function return an array, or another function. Parameters in a function call are pushed on the stack using normal C linkage conventions, ie right-to-left, to allow for functions with a variable number of parameters (eg printf, first argument specifies number and type of subsequent). Parameters should be word entities, ie int char and pointer type. Functions may be declared as extern, static or register, but these classes are ignored by the compiler. Miracle C marks functions defined in the program text as `internal' and others as `external' and generates object accordingly. Static functions (visible only within a program) are not supported (yet!). Miracle C needs functions with no parameters to be declared by int afunc(); It does not follow the ANSI convention of declaring such as function by int afunc(void); nor does it follow the traditional C convention of declaring a function's return type, but omitting a declaration of parameters, using the above syntax. Parameters in a definition count as local variables in the highest level block in the function body, following the ANSI definition. Storage classes are not allowed for parameters in a definition. Functions with return type `void' must not return a value. A function declared to have return type `int' need not specify return type in the function definition, so we allow int main(); .... main() { ... } Function prototypes may not be nested; void (*signal(int sig, void (*func)(int sig)))(int sig); is illegal, but can circumvent this by typedef void sig_handler(int sig); sig_handler *signal(int sig,sig_handler *func); Autoprototyping of functions is allowed. If a function call is introduced for a function which has not been declared, a declaration is automatically generated for the function. The automatically generated prototype will use the types of parameters which are given to the function on call, and will assume a function return type of int. If the desired function type is different to that which will be implicitly generated from the function call then an explicit declaration should be made before the function is called. Function autoprototyping may be disabled by a compiler flag. Types Miracle C supports void, scalars (pointers, signed and unsigned char int long), enumeration types, floating point types, pointers to any object, struct/union and multi-dimensional array types. It also has `function types' (see the section on functions). As a small-model 8086 compiler, CC supports 16-bit ints and pointers, 8 bit char values (expanded to 16 bits when passed as parameters on the stack), and 32 bit longs. The type `short' is a synonym for `int'. An object declared by signed avar; is a signed integer. Floating point numbers are not implemented. Long integers are not completely implemented; for example, adding a long to an int is not allowed, but adding two longs is (so cast the int to long before adding); and long values should not be passed as function arguments. Miracle C maintains a type value for each expression, computed from the types of its components; a data size is associated with each type value.Traditional C casts are allowed, but type checking of assignments etc is not strict. Array subscripts, and adding an int to a pointer, is scaled up according to the data size of the type being pointed to, eg if intptr is int * then intptr+1 (or intptr[1]) will point to intptr plus 2 in memory. Arrays of any type except void can be declared and can be multi-dimensional. Void variables are not allowed. Enumerations are introduced by an `enum' declaration, such as enum colour { red, green, blue } mycolour; enum colour anothercolour. The tag (here `colour') is optional, and can be used in a later declaration. Enumerations must start at zero, the traditional "red=2" syntax is not supported. An enum variable is int. The enum namespace is separate from others, hence enum one { one, two } number; is allowed. Record (Struct/Union) Structure (record) type is a collection of named components; struct structag { int x, y[3]; char z; struct structag *sptr, *tptr; } sarray[10], *sptr; struct structag another, *anotherptrs[5]; Structure tag `structag' identifies this structure type for other declarations. The struct has components x,y,z and two pointers to other `structag' structs (so we've declared a tree structure type). Structure declarations cannot be nested (that would be infinitely silly). Structure component names live in a separate namespace for each struct/union type, hence we can introduce variables with the same name. Struct components occupy successive memory locations, and the size of a struct (as given by sizeof) is the sum of the sizes of it's components. Struct components are referred to using . and -> operators. In the example above we declare sarray an array of 10 `structag' structs and allocate space for it (either static or on the stack). Direct reference by sarray[i].y[0], indirect reference via pointer to struct by sptr->z, sptr->tptr->y[i]. A union is declared using the same syntax as struct, but contains only one of its members at any one time (so the size of a union is the max of the size of it's components, and all items are at offset zero in the union). Nested structs/unions are allowed. Bit fields in structs aren't implemented. Struct bitfields are permitted. A single bitfield must not exceed the capacity of a machine word (16 bits). All the normal arithmetic operations and assignment are permitted on bitfields, as are struct bitfield initializers. Typedef User defined type synonyms are introduced by the `typedef' statement; typedef int *ptr, (*func)(), afun(); ptr wordptr; afun main; func funcptr=main; ptr `ptr to int' func `ptr to function: void->int' afun `function: void->int' Typedef doesn't create a new type, only type synonyms, so type compatibility/comparison works. Typedef for function can include function prototypes; typedef int funci(char *x); funci funky; A typedef name may be global, or local to a function. The ANSI standard allows typedefs to be redeclared in a block, but CC doesn't (yet). Type Compatibility Two expressions are assignment compatible t1=t2 if t1 is an lvalue, and; - t1 is (u)long, t2 is (u)int/char, extended to 32 bits - t1 is (u)int/char, t2 is (u)long, truncated to 16 bits - t1 and t2 are both (u)long, 32 bit assignment - t1 is (u)char, 8 bit assignment else 16 bit assignment (of int, pointer etc) Bitfields may be signed or unsigned integer quantities and have the same type compatibility rules as integers. Some operators eg || && ^ | & * / % require (u)int operands. Pointers may be subtracted (t1-t2 yields a pointer of type t1) but not added (addition of pointer is meaningless). Array types count as ptrs, depth of ptr=dimensionality of array. Struct/unions types are compatible if they have the same struct tag. Declarations Variables, functions and user defined data types are introduced by declaration statements. A declarator gives identifier name and type; Scalar int x Floating point double x; Pointer int *x; char *y[]; (y is pointer to array, same as char **y;) Array int (*x)[4]; (x is pointer to array of 4 ints) Function int x(), y(int a,char *b), (*x[])(int a); To parse a declaration, start from name and work out according to precedence rules; int *a[10] a is array of 10 ptrs to int int (*x[])(int a) x is array of ptrs to functions: int- >int Global variables are visible throughout the program from the point of declaration, unless they are hidden by a local declaration with the same name. Local variables are visible throughout the block in which they are defined, unless they are hidden by a declaration at an inner level with the same name. Globals are extern by default. Static variables (globals and local statics) have storage allocated at compile time in static data area to hold a possible initializer. Local variables are allocated on the stack at runtime and are local to a block, or are function parameters. Parameters to a function are considered as being declared at the topmost local level in the function, so a local declaration using a parameter name is an error. Local objects may not be declared more than once. All floating point types (float, double and long double) are treated internally as 8-byte double quantities. Normal floating point arithmetic is allowed on these quantities. No support for numeric coprocessing is included in this version. Initializers are allowed for both global and local objects and follow traditional syntax. Global initializers are evaluated at compile-time, a constant is stored in the static data segment. Therefore, expressions in global initializers can only contain things which can be evaluated at compile-time, such as constants and address of objects. Local initializers are evaluated when a function is called, so expressions may (even) contain function calls. Multi-dimensional arrays, structs/unions, arrays of structs and initializers for them are supported using traditional C syntax. A struct tag can be declared before any variables are; struct S { int a, b; }; struct S fred; A struct can be declared without tag or variables; struct { int a,b; }; is allowed but pointless. Also static struct S { int a,b; }; is allowed but `static' is meaningless. A global variable may be declared more than once, but all declarations must agree on type. A local variable may only be declared once. Global arrays must have the same dimensions when redeclared, but the first dimension need not be specified; char uu[5][4][3]; char uu[][4][3]; Only one definition may exist for a variable, so a global may have only one declaration with an initializer. Redeclaration of functions is not supported. Type definitions, structs/unions and enumerated types are supported; see the section on `types'. Global variables are allocated in a static data area. Uninitialized globals are set to zero; uninitialized parts of partly initialized globals are zeroed. Initialized globals give PUBDEF records for the linker, uninitialized globals give COMDEF records. Arrays are allocated at compile-time (static) or run-time (auto), hence static char p1[]="hello" allocated 6 bytes and copies "hello" into it, but pointer initialization is to run-time objects, hence char *p2="hello" allocates 2 bytes for p2 and points it to a static string item. Statement labels are local to a function body, and the target of goto statements here: ... goto here; Forward references are allowed if; - statement label may be used by goto before it is declared - an identifier should be visible immediately after declaration, eg int fred=sizeof(fred) but this is not yet implemented - struct can contain pointer to instance of itself - function declaration and use before definition Overloading of identifiers permits an identifier to have different meanings depending on context; - macro names are defined and used by the preprocessor - struct tag names have a separate name space - enum tag names have a separate name space - typedef, enum and label names are checked before variable names. a compile error results if a variable is declared with the same name as a typedef or enum name, or goto-label - struct component names are specific to a struct/union type, so two structs can have components of the same name External names must be defined at top-level (global variables). Extern declarations inside functions are not supported and are treated as declarations of internal variables. Global variables are defined to the linker and accessible to other programs. The storage classes auto and register are ignored by the compiler. Static variables local to a function are allocated in the static data area, and should have constant initializers. Initializers The compiler allows initialization of scalars, strings, struct/unions and arrays. An initializer sets the initial value of a variable, at compile time for static objects (globals or local statics) or run time for automatic variables. If no initializer exists for a global variable, it's set to zero; if none exists for a local, it's initial value is unpredictable. The traditional C syntax for initializers is supported; int x=4; struct { int a; char *s; int b; } icky = { 1, "astr", 2 }; enum { red, blue } colour = blue; Union initializer is for the first component of the union. Structs and multi- dimensional arrays are initialized recursively; int m1[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; The `shape' of an initializer (brace structure) must match that of the object being initialized. If there aren't enough initializing items for an array or struct initializer, the rest of the object is zeroed. If there are too many, a compile time error results. If the outermost dimension of an array is unspecified, it's taken from the initializer; char a[] = { 'a', 'b', 'c', '\0' }, *b="hello"; Braces can be dropped in the initializer; int a[2][3]={1,2,3,4,5,6}; the number of items in the initializer must not exceed the declared object. Static variable initializer expressions contain only objects which can be evaluated at compile-time, ie constants and addresses of static objects. The address of a global variable cannot be found if it is declared but not defined; int a, *b=&a; won't compile, but int a=2, *b=&a; is allowed. Local variable initializer expressions are evaluated at runtime, when a function is called. They can contain any expression (even function calls). If goto jumps to a label in a block, initializers for that block don't run. Initializers are permitted for floating point variables, and bitfields in struct (record) types, using the standard C syntax. Expressions An expression consists of operators acting on other expressions, or base values (variables or constants). An expression is either an lvalue or an rvalue; an lvalue refers to an object in memory, eg a variable; an rvalue is a non-lvalue. Lvalues can be `variable' e[k] lvalue.name e->name *e they are used by & ++ -- assignment-operators Expressions are formed from operators, in precedence order: , sequential evaluation, yields second operand = += -= *= /= %= &= |= ^= >>= <<= assignment assign (u)long = (u)(int|char) (u)long = (u)long (u)(int|char) = (u)long word/byte lvalue = word/byte rvalue ?: conditional exp1 ? exp2 : exp3 depending on value of exp1, yields exp2 or exp3 type of exp1 must be int types of exp2 and exp3 must match, and must be byte or word || logical or exp1 || exp2 only evaluate exp2 if exp1 false && logical and exp1 || exp2 only evaluate exp2 if exp1 true | bit or ^ bit xor & bit and bit or, xor, and take (u)int operands and yield (u)int result == != compare, signed or unsigned < > signed if at least one operand is signed, both are int or char <= >= unsigned if both operands are unsigned int or char, or pointers << >> shift, arithmetic for signed, logical for unsigned + - add sub (u)(char|int), (u)(char|int) ptr, (u)(char|int) scale up by size of object pointed to (u)long, (u)long can subtract two pointers giving an integer * / % mul div rem take (u)int operands, yield (u)int result (type) cast cannot cast to/from structs/voids cannot cast between chr and ptr, but can between int and ptr if casting long->int then lose high word, int->long high word zero * indirection & address of - unary minus ! logical not ~ bit not sizeof sizeof(typename) size of type sizeof("string") size of constant string + 1 sizeof(array-expr) size of array in bytes sizeof(expr) sizeof type of expr ++ -- post/pre decr/incr if pointer, scaled up by size of object pointed to -> indirect selection by pointer (struct/union member or bitfield) . direct selection (struct/union member or bitfield) f(..) function call, where f is a function designator f has function type and identifies a function (expr) brackets a[k] array subscript; type of k = (u)(int|char) the result of an array subscript is an lvalue only if a is a pointer, or we have reached the last array dimension for example, if int a[3][2]; then a[1]=4; is meaningless and is flagged as an error but this means that int a[2][3], **x; x=&a[1]; will not compile. The ANSI solution (`modifiable lvalues') is not implemented. number constant string constant variable global, local, lstatic Initializer constant expressions are arithmetic constant expressions and address constant expressions (not fully implemented in CC). These can have normal operators & casts, which are evaluated at compile-time. Statements All traditional C statements are implemented, except for `continue'. A statement can be one of the following; expr expression; (discarded) null null statement (actually a null expression) label label: stmt; which can be a goto-label, case-label, or default-label in switch goto goto name; where `name' is a goto-label defined somewhere in function block { decl-list; stmt; ... stmt; } A block starts with zero or more declarations, and contains zero or more statements. Blocks may be nested. Names declared within the block hide declarations of the same name outside the block, and are visible throughout the block unless hidden by a declaration at an inner level. Goto a label inside a block jumps past local variable allocation and initialization code; but the locals are de-allocated at end of block anyway, so don't jump into a block! if if (expr) stmt if (expr) stmt1 else stmt2 NB else belongs to nearest if if `expr' evaluates to non-zero, do stmt1 (else do stmt2) `else' belongs to the nearest `if' while while (expr) stmt continue executing `stmt' while `expr' evaluates to non-zero do do stmt while(expr) do `stmt' at least once, then while `expr' evaluates to non-zero for for(expr1; expr2; expr3) stmt each of exp1,2,3 optional expr1= evaluated once at start of loop expr2= continuation condition, tested before stmt executed expr3= iteration expression, evaluated after statement if expr2 omitted then it defaults to true eg. for(;;) is an infinite loop switch switch(expr) { case n1: ...; case n2: ...; ... default: ...; } case labels must be numbers or char literals; case labels should not be duplicated, at most one default exists; when a case or default is chosen, execution continues until break or end of block break terminate smallest enclosing while. do, for, switch continue continue smallest enclosing while. do, for return return opt-expr return from function with optional value void functions must not return a value ASSEMBLER PROGRAMMING INTERFACE Miracle C functions can call, and be called by, assembler routines. Function call is by pushing arguments onto the stack in right to left order, and call (pushes ip on stack). The called function pushes bp and allocate stack space for local variables. Function return is the reverse; deallocate local variables, pop bp, return; caller pops arguments. +-------+ | arg n | | . | | . | | arg 1 | | ip | | bp | | local | | . | | . | | local | +-------+ At entry to a C function, SP points to the return value of IP on the stack. To access word arguments; push bp mov bp,sp mov AX,[bp+2] ; first argument mov BX,[bp+4] ; second argument ... pop bp mov ax,retvalue ; return value ret FUNCTION LIBRARY Startup code The startup code initializes segment registers, sets argc/argv parameters to zero, and calls function `main'. If `main' returns to the startup routine, an exit handler is called which closes files and terminates. Include header files The following header files are supplied with the compiler in the 'include' subdirectory. ctype.h Prototypes for alphanumeric test and conversion functions. io.h Prototypes for elementary file functions. stdio.h Prototypes for higher level file handling and input/output functions. string.h Prototypes for memory and string handling functions. system.h Prototypes for memory and other functions. Library Functions The Miracle C library contains functions for string handling, file operations, input/output formatting, memory allocation and other functions. abs, labs #include int abs(int i) long labs(long i) Produces the absolute (positive or zero) value of its argument. abs takes an int argument returning an int, labs takes a long argument and returns a long. calloc #include void *calloc(int nitems, int itemsize) Allocates a block of memory that ia nitems * itemsize bytes in size from the heap. The initial contents of the block is zeroed. If not enough memory is available to satisfy the request a null pointer is returned. Since the compiler uses the small memory model, memory requests should be made accordingly. Allocated memory should be freed explicitly when it is no longer required. Example #include #include void main() { char **buffer; int nitems=100; buffer = calloc(nitems,sizeof(char *)); /* allocate 50 pointers to char */ if(buffer==NULL) { printf("out of memory\n"); exit(1); } printf("calloc allocated %d items of %d at : %x\n",nitems,sizeof(char *),buffer); free(buffer); return; } close #include int close(int fd) Close the file given by file descriptor handle `fd' freeing the file descriptor for use by another file. close does not write an eof character 26. Returns 0 if successful, otherwise -1 if failed. Example #include #include #include void main() { int fd; if((fd = open("tfile",O_RDONLY))<0) { printf("failed to open tfile\n"); exit(1); } printf("close code %d\n",close(fd)); return; } create #include int create(char *fname) Create a file with name `fname'. If successful, returns a file descriptor for the newly created file. Otherwise returns -1 if unsuccessful. Example #include #include void main() { int n; n = create ("tfile"); if (n == -1) { printf("Cannot create tfile\n"); } return; } exit #include void exit(int code) Closes files, flushes all output buffers and terminates with return code `code'. Example #include #include #include void main() { int n; n = create ("tfile"); if (n == -1) { printf("Cannot create tfile\n"); exit(1); } exit(0); } fclose #include int fclose(FILE *fp) Close an open file, and flush output buffer for the file. Returns 0 if successful, EOF if not. Example #include #include void main() { FILE *fp; if((fp = fopen("tfile","r"))==NULL) { printf("failed to open file tfile\n"); return; } else { fclose(fp); printf("closed file tfile\n"); } return; } feof #include int feof(FILE *fp) Tests end of file condition for file fp. Returns non-zero if end of file. After an EOF condition no further reads should be performed. Example #include #include void main() { FILE *fp; char buffer[100]; fp = fopen ("tfile", "r"); while ( !feof(fp) ) fgets(buffer, 100, fp); return; } fflush #include int fflush(FILE *fp) Flush buffer for an output file. If the file is open for writing the output buffer is wriiten to disk. If it is open for reading the buffer is cleared and another read operation is forced to occur. Returns 0 is operation successful, otherwise EOF if an error occurred. Example #include #include void main() { FILE *fp; if((fp = fopen("tfile","w"))==NULL) exit(1); fflush(fp); } fgetc #include int fgetc(FILE *fp) Reads and returns a character from a file. Returns next character or EOF. Example #include void main() { FILE *fp; fp = fopen ("tfile", "r"); while ( !feof(fp) ) putchar(fgetc(fp)); return; } fgets #include char *fgets(char *buf, int n, FILE *fp) Get a string (maximum n characters) from file `fp' to buffer `buf'. Returns NULL if an error occurred or no characters were read, otherwise returns the (null-terminated) string. Example #include #include void main() { FILE *fp; char buffer[100]; fp = fopen ("tfile", "r"); while ( !feof(fp) ) fgets(buffer, 100, fp); return; } fopen #include FILE *fopen(char *name, char *mode) Open a file with filename `name' returning a pointer to FILE. The following modes are allowed; `r' open file for reading only `w' open file for writing `a' open file for append; position at end of file `r+' open file for reading and writing `a+' `w+' create new file for reading and writing Example #include #include void main() { FILE *fp; if((fp = fopen("tfile","r"))==NULL) { printf("failed to open file tfile\n"); return; } else { fclose(fp); printf("closed file tfile\n"); } return; } fprintf #include int fprintf(FILE *fp, char *fmt, ...) Print formatted values to file. Arguments follow the format string and are interpreted according to the format string. fprintf writes its characters to the file stream fp. The format string is a sequence of characters with embedded conversion commands. Characters at are not part of the conversion command are output. Conversion commands are the same as for the printf function. fprintf returns the number of characters written. Example #include #include void main() { FILE *fp; char *msg = "number formats are: "; int n = 42; fp=fopen("con","w"); fprintf(fp,"%sx: 0%x d: %d o: %o\n",msg,n,n,n,n); fclose(fp); return; } fputc #include int fputc(int c, FILE *fp) Write a character c to file fp. Returns the character written. Example #include #include void main() { FILE *fp; char *buffer = "this text is written to console"; fp=fopen("con","w"); while(*buffer) fputc(*buffer++, fp); fclose(fp); return; } fputs #include int fputs(char *str, FILE *fp) Write a string str to file stream fp. Returns non-negative if successful, EOF if error occurred. Example #include #include void main() { FILE *fp; char *buffer = "this text is written to console"; fp=fopen("con","w"); fputs(buffer,fp); fclose(fp); return; } fread #include int fread(void *buf, int sizelem, int n, FILE *fp); Read `n' items, each of size `sizelem' from file `fp' into buffer `buf'. Returns the number of complete elements read. Example #include #include #include void main() { FILE *fp; char *dest; int n; if((fp = fopen("tfile","r")) == NULL) return; dest = calloc(81,1); n = fread(dest,1,80,fp); printf("read %d bytes\n%s",n,dest); return; } free #include void free(void *ptr) Free memory block pointed to by `ptr'. The memory block described by ptr must have been allocated by calloc, malloc or realloc. Example #include #include void main() { char *p; if((p = malloc(100)) == NULL) { printf("out of memory\n"); return; } free(p); return; } fscanf #include int fscanf(FILE *fp, char *format,...); Read characters from file `fp' and convert according to format string `format', storing via argument pointers which follow `format'. See the description of`scanf' for input format specification. Returns the number of arguments read from input. Example #include #include void main() { FILE *fp; char fst[10], sec[20]; int n; fp=fopen("con","r"); fscanf(fp, "%s %s %d",fst,sec,&n); printf("You typed %s %s %d\n",fst,sec,n); return; } fwrite #include int fwrite(void *buffer,int sizelem, int n,FILE *fp); Write `n' items, each of size `sizelem' to file `fp' from buffer `buf'. Returns the number of complete elements written. getc #include int getc(char *fp) Get a single character from file `fp'. Returns the character, or EOF if input error. Example #include void main() { int n; FILE *fp; fp = fopen ("tfile", "r"); while ( !feof(fp) ) putchar(getc(fp)); return; } getchar #include int getchar() Get a single character from stdin. Returns the character, or EOF if input error. Example #include void main() { int n; FILE *fp; while ((n=getchar())!=EOF) putchar(n); return; } gets #include char *gets(char *buf) Get string from stdin into buffer `buf'. Reads characters from file until newline or end-of- file. The string written to `buf' is null-terminated. If a string was read into the buffer it's returned, else returns NULL. Example #include void main() { char buf[80]; gets(buf); puts(buf); return; } is-ctype #include int isxx(char c) Test a character to see if it's of a specified type. If it is, a non-zero value is returned; if not, zero is returned. isalnum alphanumeric, letter or digit isalpha alpha, letter isascii ascii, 0-127 iscntrl control character isdigit digit isgraph graphic, printable islower lowercase letter isprint printable ispunct punctuation isspace space character isupper uppercase letter isxdigit hex digit malloc #include void *malloc(int n) Allocate buffer of n bytes from the heap, Returns address of buffer, or NULL if no memory available. Example #include #include void main() { char *p; if((p = malloc(100)) == NULL) { printf("out of memory\n"); return; } free(p); return; } memchr #include void *memchr(void *buf, int c, int count); Search buffer `buf' for a character `c'. The search stops when a character `c' is found, or after `count' bytes. memcmp #include int memcmp(void *buf1, void *buf2, int n); Compare data in buffers `buf1' and `buf2', of size `n'. Returns = 0 buf1 and buf2 hold identical data < 0 first differing byte in buf1 < buf2 > 0 first differing byte in buf1 > buf2 memcpy, memmove #include void *memcpy(void *buf1, void *buf2, int count); void *memmove(void *buf1, void *buf2, int count); Copy `count' bytes from buffer `buf2' to buffer `buf1'. Returns `buf1'. memset #include void *memset(void *buf, int val, int n); Sets contents of buffer `buf' of size `n' to value `val'. Returns buffer `buf'. open #include int open(char *name, int mode) Open a disk file `name' using read/write mode `mode', which can be O_RDONLY, O_WRONLY, O_RDWR returns file handle for the opened file, or EOF if there was an error opening the file. The mode may be 0 read-only O_RDONLY 1 write-only O_WRONLY 2 read-write O_RDWR Example #include #include void main() { int fd; if (EOF == (fd = open("tfile",O_RDWR))) { printf ("failed to open tfile"); } return; } printf #include int printf(char *fmt, ...) Print formatted values to screen. Arguments follow the format string and are interpreted according to the format string. The format string is a sequence of characters with embedded conversion commands. Characters that are not part of the conversion command are output. printf returns the number of characters written. The format string can contain text values, or a format specification for arguments, one per argument, beginning with a `%' character, the type matching the argument's type; % (conversion-flag) (min-field-width) (precision) (operation) eg %-20s %12.4d The conversion flag character can be one of - left adjust + force sign output 0 pad with 0 instead of space (space) produce sign `-' or space Minimum field width is the minimum number of characters output for the field; if fewer characters are available from the argument, pad characters (0 or space) are inserted. Precision is the minimum number of characters printed from a number, or the maximum number of characters printed from a string. Operation specifies the expected argument type and what will be output; the expected output type must match the type of the corresponding argument for output to be meaningful. `h' short int `l' long int `c' character `d' `i' integer `o' octal integer `p' pointer `x' hexadecimal `s' string `u' unsigned The number of characters written is returned. Example #include #include void main() { char *msg = "number formats are: "; int n = 42; printf(fp,"%sx: 0%x d: %d o: %o\n",msg,n,n,n,n); return; } putc #include int putc(int c, FILE *fp) Write character `c' to file `fp'. Returns `c'. putchar #include int putchar(int c) Write character `c' to stdout. Returns `c'. Example #include void main() { int n; FILE *fp; fp = fopen ("tfile", "r"); while ( !feof(fp) ) putchar(getc(fp)); return; } puts #include int puts(char *str) Writes string `str' to stdout, then writes a newline character. Example #include void main() { char buf[80]; gets(buf); puts(buf); return; } read #include int read(int fd, void *buf, int n) Read `n' bytes from file given by file descriptor `fd' into buffer `buf'. Direct DOS read from file. Returns -1 error 0 end of file n number of bytes read Example #include #include void main() { char buf[80]; int fd; if (EOF == (fd = open("tfile",O_RDWR))) { printf ("failed to open tfile"); } read(fd,buf,80); puts(buf); return; } scanf #include int scanf(char *format,...); Read arguments from stdin. Parse it according to specification in `format' string, and assign input to arguments following the format string. The arguments following `format' must be pointers to objects where the input is to be stored, and must match the specification in the format string. Returns the number of arguments assigned to. If no arguments were assigned, an EOF is returned. The format string may contain; - space characters; skip whitespace characters in input. - any other characters (except %) which should match input (match a `%' character by specifying `%%') - input conversion specification % (size) conversion-char If the conversion specification is %*(size) c-char then the converted input is not stored and no argument is used. If `size' is specified then at most `size' characters are converted. Otherwise conversion stops when an invalid input character is read. The conversion character specifies the input object type and must match the type of argument pointer; `h' short int `l' long int `c' character `d' `i' integer `x' hexadecimal `s' string Example #include #include void main() { unsigned n; printf("type a number: "); scanf("%i",&n); printf("number %d is %x hex",n,n); return; } sprintf #include int sprintf(char *buf, char *fmt, ...) Print formatted values to memory. Arguments follow the format string and are interpreted according to the format string. sprintf returns the number of characters written. See printf for description of format string. sscanf #include int sscanf(char *buf, char *fmt, ...) Parse string in buffer `buf' according to specification in `format' string, and assign input to arguments following the format string. The arguments following `format' must be pointers to objects where the input is to be stored, and must match the specification in the format string. Returns the number of arguments assigned to. If no arguments were assigned, an EOF is returned. strcat #include char *strcat(char *buf1, char *buf2) Catenate null-terminated string in `buf2' to the string in buffer `buf1'. Returns `buf1'. strchr #include char *strchr(char *str, int c) Search string `str' for character `c', return first occurrence. strcmp #include int strcmp(char *str1, char *str2) Compare null-terminated strings `str1' and `str2'. If they are identical then return 0. If the first differing character in `str1' is greater than that in `str2' then return a positive value, else return a negative value. strcpy #include char *strcpy(char *buf1, char *buf2) Copy null-terminated string in `buf2' to buffer `buf1'. strdup #include char *strdup(char *str) Create copy of null-terminated string `str' in newly allocated buffer, and return the buffer. strlen #include int strlen(char *str) Return length of null-terminated string `str'. strlwr #include char *strlwr(char *str) Convert string `str' to lowercase and return it. strncat #include char *strncat(char *buf1, char *buf2, int n) Catenate null-terminated string in `buf2' to null-terminated string in `buf1'. At most `n' bytes are catenated. Returns `buf1'. strncmp #include int strncmp(char *buf1, char *buf2, int n) Compare null-terminated strings `str1' and `str2'. The strings are compared for at most `n' characters. If they are identical then return 0. If the first differing character in `str1' is greater than that in `str2' then return a positive value, else return a negative value. strncpy #include char *strncpy(char *buf1, char *buf2, int n) Copy null-terminated string in buffer `buf2' to buffer `buf1'. Exactly `n' characters are copied. If `buf2' contains fewer than `n' characters, destination `buf1' is padded with nulls. strnicmp #include int strnicmp(char *buf1, char *buf2, int n) Compares `n' characters of strings `buf1' and `buf2', ignoring case. Returns 0 strings equal, ignoring case < 0 first differing character `buf1' < `buf2' > 0 first differing character `buf1' > `buf2' strpbrk #include char *strpbrk(char *str1, char *str2) Search string `str1' for a character occurring in string `str2', return first occurrence. strrchr #include char *strrchr(char *buf, int c) Search null-terminated string in buffer `buf' for character `c', returning pointer to last occurrence of `c' in `buf'. strrev #include char *strrev(char *str) Reverses the contents of string `str'. Returns `str'. strspn #include int strspn(char *str1, char *str2) Span string `str1' skipping any characters in string `str2'. strupr #include char *strupr(char *str) Convert string `str' to uppercase, and return uppercased string. tolower, toupper #include int tolower(int c) int toupper(int c) Convert character `c' to lower/upper case. ungetc #include int ungetc(int c, FILE *fp) Push character `c' back to input file `fp'. Only one character may be ungetc'd. Returns `c' if succeeded, else EOF. Example #include #include #include void main() { FILE *fp; char c; fp = fopen("tfile","r"); while((c = fgetc(fp)) != EOF) if(isspace(c)) break; else putchar(c); ungetc(c,fp); fclose(fp); return; } vsprintf #include int vsprintf(char *buf, char *fmt, char *args) Prints the arguments pointed to by stack segment pointer `args' to output buffer `buf'. See printf for description of format string, Returns the number of characters written. write #include int write(int fd, void *buffer, int size); Write memory buffer of length `size' to file `fd'. This is a direct DOS write to file. Returns the number of characters written or -1 if error. Example #include #include #include void main() { int n, fd; char *buf = "test data to be written"; if((fd = open("tfile",O_WRONLY)) == -1) { puts("failed to open file"); return; } n = write(fd,buf,strlen(buf)); printf("%u bytes written\n",n); close(fd); return; }