SoFunction
Updated on 2025-04-06

Recommended C Style and Coding Standards Chinese translation page 3/3



16. Portability

"C language combines the power of assembly and portability" - Anonymous, alluding to Bill Sack.

The benefits of portable code are obvious to all. This section will explain some guidelines for writing portable code. Here "portable" means that a source code file can be compiled and executed on different machines, the premise is that different header files may be included on different platforms and use different compiler switch options. The #define and typedef included in the header file may vary from machine to machine. Generally speaking, a new "machine" refers to a different hardware, a different operating system, a different compiler, or any combination of these. Reference 1 contains a lot of useful information about style and portability. Here is a list of hidden dangers that you should consider avoiding when designing portable code:

* Write portable code. The details of optimization are considered only if proven necessary. Optimized code is often vague and difficult to understand. Optimized code on one machine can get worse on other machines. Record the performance optimization methods used and localize as much as possible. The documentation should explain how these methods work and why they were introduced (e.g. "The loop was executed several times")

* Realize that many things are inherently unportable. For example, code that handles specific hardware registers such as program status words, and code that is designed to support certain hardware components, such as assemblers and I/O drivers. Even in this case, many routines and data can still be designed to be machine-independent.

* When organizing source files, put machine-independent codes in different files. After that, if the program needs to be ported to a new machine, we can easily determine which needs to be changed. Add comments to the code related to machine dependencies in the header files of some files.

* Any "implementation-related" behavior should be treated as a machine (compiler) dependency. Suppose the compiler or hardware implements it in a very weird way.

* Pay attention to the length of the machine. The size of objects may not be intuitive, the pointer size is not always the same as the integer size, nor is it always the same size as each other, or can be freely converted to each other. The following table lists the sizes (in bits) of C basic types under different machines and compilers.

Copy the codeThe code is as follows:

type    pdp11  VAX/11 68000  Cray-2 Unisys Harris 80386
        series       family          1100   H800
char     8      8      8       8      9     8      8
short    16    16     8/16   64(32)   18    24    8/16
int      16    32     16/32  64(32)   36    24    16/32
long     32    32      32    64       36    48    32
char*    16    32      32    64       72    24    16/32/48
int*     16    32      32    64(24)   72    24    16/32/48
int(*)() 16    32      32    64       576   24    16/32/48

Some machines may have more than one size for a certain type. Its type size depends on the compiler and different compile-time flags. The following table shows the "safe" type sizes for most systems. Unsigned and signed numbers have the same size (unit: bit).

Copy the codeThe code is as follows:

Type    Minimum  No Smaller
          # Bits   Than
char       8 
short      16    char
int        16    short
long       32    int
float      24 
double     38    float
any *      14 
char *     15    any *
void *     15    any *

* The void type can ensure that there is sufficient bit precision to represent a pointer to any data object. The void()() type can be guaranteed to represent a pointer to any function. These types can be used when you need general pointers (in some old compilers, they are represented by char and char()() respectively). Make sure to convert these pointer types back to the correct type before using them.

* Even if an int and a char type are the same size, they may still have different formats. For example, the following example may fail on some machines where sizeof(int) is equal to sizeof(char). The reason is that with the free function expects a char, but passes in an int.

Copy the codeThe code is as follows:

    int *p = (int *) malloc (sizeof(int));
   free (p);

* Note that the size of an object cannot guarantee the accuracy of this object. Cray-2 may use 64 bits to store an integer, but a long integer may be truncated to 32 bits after being converted to an integer and then converted back to a long integer.

* Integer constant 0 can be forced to transform to any pointer type. The converted pointer is called a null pointer corresponding to that type and is different from other pointers of that type. Null pointer comparisons are always equivalent to constant 0. A null pointer should not be compared with a variable with a value of 0. Null pointers are not always represented using all zero bit mode. Two different types of null pointers may sometimes be different. A null pointer of a certain type is cast to a pointer of another type, and the result is that the pointer is converted to a null pointer of the second type.

* For ANSI compilers, when two pointers of the same type access the same block of storage, their comparisons are equal. When a non-0 integer constant is converted to a pointer type, they may be equal to other pointers. For non-ANSI compilers, the comparison of two pointers accessing the same block of storage may be different. For example, the following two pointer comparisons may be equal or unequal, and they may or may not have access to the same block of storage area.

Copy the codeThe code is as follows:

    ((int *) 2 )
    ((int *) 3 )

If you need a 'magic' pointer instead of NULL, either allocate some memory or treat the pointer as machine-related.

Copy the codeThe code is as follows:

extern int x_int_dummy;    /* in */
#define X_FAIL    (NULL)
#define X_BUSY    (&x_int_dummy)
#define X_FAIL    (NULL)
#define X_BUSY    MD_PTR1  /* MD_PTR1 from "" */

* Floating point numbers include both precision and range. These are all independent of the data object size. However, the value of a 32-bit floating point number varies when overflowing on different machines. Meanwhile, 4.9 times 5.1 may produce two different numbers on different machines. The differences in rounding and truncation will give particularly different answers.

* On some machines, a double-precision floating point number may be lower in accuracy or range than a single-precision floating point number.

* On some machines, the first half of the double value may be a float type with the same value. Never rely on this.

* Beware of signed characters. For example, on some VAX systems, characters used in expressions are symbolically extended, but not on some other machines. Code that has a dependency on signed and unsigned is not portable. For example, if c is assumed to be a positive value, arrayc will not work properly when c is signed and negative. If you must assume signed or unsigned characters, comment them with SIGNED or UNSIGNED. The behavior of unsigned characters can be guaranteed by an unsigned char.

* Avoid making assumptions about ASCII. If you have to assume, then record it and localize it. Remember that characters are likely to be represented by more than 8 bits.

* Most machines use 2's complement to represent numbers, but we should not take advantage of this feature in our code. Optimization of using equivalent shift operations instead of arithmetic operations is particularly questionable. If this is necessary, then the machine-related code should be defined with #ifdef, or the operation should be executed under the #ifdef macro decision. You should measure which time saves using this difficult-to-understand code is more or less than the time it takes to find bugs when doing code porting.

* Generally, if the word length or value range is very important, typedef should be used to define types with a specific size. Large programs should have a unified header file to provide a typedef definition of common, size-sensitive types, which makes it easier to modify and find size-sensitive code in emergency repairs. Unsigned types are more compiler-independent than signed integers. If you can use 16bit or 32bit to identify a counter for a simple for loop, we should use int. Because for current machines, more efficient (natural) storage units can be obtained through integers.

* Data alignment is also important. For example, on different machines, a four-byte integer may use any address as the starting address, or may only allow an even address as the starting address, or only an address with an integer multiple of 4 may be used as the starting address. Therefore, the offsets of individual elements of a particular structure vary on different machines, even if the given elements are the same size on all machines. In fact, a structure containing a 32-bit pointer and an 8-bit character may have three different sizes on three different machines. As an inference, object pointers may not be freely interchangeable; saving an integer through a pointer to the starting address with an odd address with a length of 4 bytes can sometimes work properly, but sometimes it will lead to cores, and sometimes it will fail quietly (in this process will destroy other data). On machines that do not address by bytes, character pointers are even more "areas of accidents". The alignment considerations and the particularity of the loader make it easy to think that two consecutively declared variables are also connected in memory, or that a variable of a certain type has been properly aligned and can be used as other types of variables.

* On some machines, such as VAX (little endian), the bytes of a word increase in importance as the address increases; while on others, such as 68000 (big endian), the importance decreases as the address increases. The byte order of words or larger data objects (such as a double-precision word) may not be the same. Therefore, any code that depends on the left-to-right bit pattern within an object deserves a particularly meticulous review. These bit fields are portable only if two different bit fields in the structure are not connected and are not treated as a unit. In fact, connecting any two variables is non-portable.

* There are some unused holes in the structure. Conjecture unions are used for type fraud. In particular, a value should not use one type when storing, but another type when reading. An explicit tag field may be useful for a union.

* Different compilers use different conventions when returning structures. This causes errors to occur when the code accepts structure values ​​returned from library code compiled from different compilers. Structural pointers are not a problem.

* Do not assume parameter transfer mechanism. In particular, pointer size, parameter evaluation order, size, etc. For example, the following code is not portable.

Copy the codeThe code is as follows:

        c = foo (getchar(), getchar());

    char
    foo (c1, c2, c3)
    char c1, c2, c3;
    {
        char bar = *(&c1 + 1);
        return (bar);            /* often won't return c2 */
    }

*The above example has many problems. The stack may grow upward or downward (in fact, it does not even require a stack). Parameters may be expanded upon passing in, for example, a char may be passed in int type. Parameters may be pushed into the stack in left to right, right to left, or in any order, or placed directly in a register (no stack pressing is required at all). The order of parameter evaluation may also be different from the order of stack pressing. A compiler may use multiple (incompatible) calling conventions.

* On some machines, null character pointers ((char *)0) are often treated as pointers to empty strings. Don't rely on this.

*  Do not modify string constants. Here is a notorious example

Copy the codeThe code is as follows:

    s = "/dev/tty??";
  strcpy (&s[8], ttychars);

* There may be hollows in the address space. Simply compute the address of an element in an array with unallocated space (before or after the actual storage area of ​​the array) may cause a program to crash. If this address is used for comparison, sometimes the program can run, but it will destroy the data, report an error, or fall into a dead loop. In ANSI C, pointers to an array of objects to the first element after the end of the array are legal, which is usually safe on some old compilers. However, this "outside" cannot be dereferenced.

* Only == and != comparisons can be used for all pointers of a given type. When two pointers point to an element in the same array (or the first element after the array), it is portable to compare the two pointers using <<, <=, & amp; gt; or >=. Similarly, it is portable to just use arithmetic operators on two pointers to elements within the same array (or the first element after the array).

* Word size also affects shift and mask. The following code will only clear the rightmost three bits of an integer number on some 68000 machines, and on other machines it will also clear the two bytes of the high address. x &= 0177770 Use x &= ~07 to work properly on all machines. Bitfield does not have these problems.

* Side effects within expressions may cause code semantics to be compiler-related, because in most cases the evaluation order of C language is not explicitly defined. Here is a notorious example:

Copy the codeThe code is as follows:

    a[i] = b[i++];

In the above example, we only know that the subscript value of b has not been increased. The subscript i value of a may be the value after self-increase or may be the value before self-increase.

Copy the codeThe code is as follows:

    struct bar_t { struct bar_t *next; } bar;
    bar->next = bar = tmp;

In the second example, the address of bar->next is likely to be calculated before bar is assigned.

Copy the codeThe code is as follows:

bar = bar->next = tmp;

In the third example, bar may be assigned before bar->next. Although this may go against the "assignment from right to left" rule, it is indeed a legitimate resolution. Consider the following example:

Copy the codeThe code is as follows:

long i;
short a[N];
i = old
i = a[i] = new;

The value assigned to i must be a value that is assigned in the order of processing from right to left. However, i may be assigned as "(long) (short)new" before ai is assigned. Different compilers practice differently.

* Question the value ("magic number") that appears in the code.

* Avoid preprocessor tips. Some macros such as using // sticky and strings and expanding with parameter strings can break code reliability.

Copy the codeThe code is as follows:

    #define FOO(string)    (printf("string = %s",(string)))
    …
  FOO(filename);

It just sometimes expands to

Copy the codeThe code is as follows:

 (printf("filename = %s",(filename)))

careful. A weird preprocessor may cause macro exception interruption on some machines. Here are two different implementation versions of a macro:

Copy the codeThe code is as follows:

  #define LOOKUP(chr)    (a['c'+(chr)])    /* Works as intended. */
  #define LOOKUP(c)    (a['c'+(c)])        /* Sometimes breaks. */

The second version of LOOKUP may be extended in two different ways and will cause the code to break exceptionally.

* Be familiar with existing library functions and definitions (but don't be too familiar with them. In contrast to their external interfaces, the internal details of the library infrastructure often change and have no warnings, and these details are often not portable). You should no longer rewrite string comparison routines, terminal control routines, or write your own definitions for the system structure. Doing it yourself is both a waste of your time and makes your code less readable, because another reader needs to know if you have done something special in the new implementation and try to confirm their existence. Doing so will prevent you from making full use of some auxiliary microcode or other methods that help improve the performance of system routines. Going further, it will be the source of high-yield products of a bug. If possible, be aware of the differences between public libraries (such as ANSI, POSIX, etc.).

* If lint is available, please use lint. This tool is of great value for finding machine-related structures, other inconsistencies in the code, and program bugs that have been successfully checked by the compiler. If your compiler has a switch to turn on the warning, turn it on.

* Question the label (Label) inside the code block that is associated with the switch or goto outside the code block.

Regardless of the type, the parameters should be converted to the appropriate type. When NULL is used in function calls without prototypes, convert NULL. Don't let function calls become where type spoofing happens. The C language type improvement rules are very confusing, so try to be careful. For example, if a function accepts a 32-bit long integer as a parameter, but is actually passed in an integer number of 16-bit long, the function stack may not be aligned, and this value may be raised by errors.

* Please use explicit type conversion when mixing signed and unsigned arithmetic calculations

* Cross-program goto and longjmp should be used with caution. Many implementations "forgot" to restore the values ​​in the register. Declare key values ​​as volatile as possible, or comment them as VOLATILE.

* Some linkers convert names to lowercase, and some linkers only recognize the first six letters as unique identifiers. On these systems, programs may quietly interrupt operation.

* Beware of compiler extensions. If compiler extensions are used, consider them as machine dependencies and document them in documentation.

* Usually programs cannot execute code in data segments or cannot write data into code segments. Even if the program can do this, there is no guarantee that it will be reliable.

17. Standard C

Modern C compilers support some or all of the ANSI proposed standard C. Whenever possible, try to write and run programs in standard C and use features such as function prototypes, constant storage, and volatile storage. Standard C improves program performance by providing effective information to the optimizer. Standard C improves code portability by ensuring that all compilers accept the same input language and provide relevant mechanisms to hide machine-related content or provide warnings for those machine-related code.

17.1 Compatibility

Writing code that is easy to port to old compilers. For example, some new (standard) keywords, such as const and volatile, are conditionally defined in it. The standard compiler predefined the preprocessor symbol STDC (see footnote 8). It is difficult to simply handle the void type correctly, because many old compilers only understand void but do not. The easiest way is to define a new type VOIDP (related to machines and compilers), which is usually defined as char* under old compilers.

Copy the codeThe code is as follows:

#if __STDC__
    typedef void *voidp;
#   define COMPILER_SELECTED
#endif
#ifdef A_TARGET
#    define const
#    define volatile
#    define void int
    typedef char *voidp;
#    define COMPILER_SELECTED
#endif
#ifdef …
    …
#endif
#ifdef COMPILER_SELECTED
#    undef COMPILER_SELECTED
#else
    { NO TARGET SELECTED! }
#endif

Note that in ANSI C, # must be the first non-whitespace character of the preprocessor indicator in the same line. In some old compilers, it must be the first character in the same line.

When a static function has a pre-declaration, the pre-declaration must contain a storage modifier. In some old compilers, this modifier must be "extern". For the ANSI compiler, this storage modifier must be static, but the global function must still be declared extern. Therefore, the predeclaration of a static function should use a #define, such as FWD_STATIC, and be appropriately defined by #ifdef.

A "#ifdef NAME" should either end with "#endif" or end with "#endif/NAME/" and should not end with "#endif NAME". Comments should not be used for short #ifdefs, because through code we can clarify its meaning.

ANSI's three-character group may cause mysterious interrupts to the program that contains strings of ??.

17.2 Format

ANSI C's code style is the same as regular C, but with two surprises: storage modifiers and parameter lists.

Since the binding rules for const and volatile are strange, each const or volatile object should be declared separately.

Copy the codeThe code is as follows:

int const *s;        /* YES */
int const *s, *t;    /* NO */

A function with a prototype combines parameter declarations and definitions into a parameter list. Comments for each parameter should be provided in the comments of the function.

Copy the codeThe code is as follows:

/*
 * `bp': boat trying to get in.
 * `stall': a list of stalls, never NULL.
 * returns stall number, 0 => no room.
 */
int
enter_pier (boat_t const *bp, stall_t *stall)
{
    …

17.3 Prototype

Function prototypes should be used to make the code more robust and runtime performance better. Unfortunately, it's the prototype's statement

Copy the codeThe code is as follows:

extern void bork (char c);

Incompatible with the definition.

Copy the codeThe code is as follows:

void
bork (c)
char c;
 …

In the prototype, c should be passed in as the most natural type on the machine, most likely a byte. Instead of the non-prototyping (backward compatible) definition implies that c is always passed in as an integer. If a function has type-promoting parameters, the caller and the callee must compile equally. Either you must use function prototypes or you must not use prototypes. If parameters can be promoted in programming, then the problem can be avoided, for example, bork can be defined to accept an integer parameter.

If the definition is also prototyped, the above statement will work fine.

Copy the codeThe code is as follows:

void
bork (char c)
{
    …

Unfortunately, the prototyped syntax will cause non-ANSI compilers to reject this program.

But we can easily adapt both prototypes and old compilers by writing external declarations.

Copy the codeThe code is as follows:

#if __STDC__
#    define PROTO(x) x
#else
#    define PROTO(x) ()
#endif

extern char **ncopies PROTO((char *s, short times));

Note that PROTO must use double-layer brackets.

Finally, it is best to write code in only one style (for example, using a prototype). When a non-prototyping version is required, an automatic conversion tool can be generated.

17.4 Pragmas

Pragmas is used to introduce machine-related code in a controlled way. Obviously, pragma should be considered machine-related. Unfortunately, the syntax of ANSI pragmas makes it impossible for us to isolate it into machine-related header files.

Pragmas is divided into two categories. Optimization-related can be safely ignored. Pragmas that affect system behavior (need to pragmas) cannot be ignored. The required pragmas should be used in conjunction with #ifdef, so that if no pragma is selected, the compilation process will exit.

Two compilers may use the same given pragma in two different ways. For example, a compiler might use haggis to emit an optimization signal. And another might use it to imply a specific statement that once executed, the program should exit. However, once pragma is used, they must always be surrounded by machine-related #ifdefs. For non-ANSI compilers, Pragmas must always be #ifdef. Make sure to indent #pragma's # otherwise some older preprocessors will hang when they process it.

Copy the codeThe code is as follows:

#if defined(__STDC__) && defined(USE_HAGGIS_PRAGMA)
    #pragma (HAGGIS)
#endif

"The '#pragma' command described in the ANSI standard has the effect of arbitrary implementation definitions. In GNU C preprocessing, '#pragma' first attempts to run the game 'rogue'; if it fails, it will try to run the game 'hack'; if it fails, it will try to run the GNU Emacs showing the Tower of Hannover; if it fails, it will report a fatal error. Anyway, the preprocessing will no longer continue."

— GNU CC 1.34 C preprocessing manual.

18. Special considerations

This section contains some miscellaneous items: 'do' and 'not do'.

* Do not change the syntax through macro replacement. This will make the procedure difficult for everyone to understand, except that perpetrator.

* Do not use floating point variables where discrete values ​​are needed. Using a floating point number as a loop counter is undoubtedly shooting oneself in the foot. Always test floating point numbers with <= or >=, and never use exact comparisons (== or !=).

* The compiler also has bugs. Common and highly common problems include structure assignment and bit fields. You cannot predict generally what bugs a compiler will have. But you can avoid using known structures in your program that have problems on all compilers. Any code you can't make it useful, you may still encounter bugs, and the compiler will likely be fixed during this time. Therefore, you should only write code "around" the compiler bug when you are forced to use a specific compiler full of bugs.

* Don't rely on automatic code beautification tools. The main beneficiary of a good code style is the code writer, and especially in the early design stages of handwritten algorithms or pseudo-code. Automatic code beautification tools should only be used when completed, syntax correct, and then cannot meet requirements when blanks and indents are more concerned. With a focus on the details of meticulous programmers, programmers will do better for those who explain the function or file layout clearly (in other words, some visual layouts are determined by intention rather than syntax, and beautification tools cannot understand the programmer's thinking). Careless programmers should learn to be a meticulous programmer, rather than relying on beautification tools to make the code readable better.

* The unexpected oscillation of the second = in the logical comparison expression is a common problem. Use explicit tests. Avoid using implicit tests for assignments.

Copy the codeThe code is as follows:

abool = bbool;
if (abool) { …

When the embedded assignment expression is used, make sure the test is explicit so that it cannot be "fixed" later.

Copy the codeThe code is as follows:

while ((abool = bbool) != FALSE) { …
while (abool = bbool) { …    /* VALUSED */
while (abool = bbool, abool) { …

Explicitly comment on variables that are modified outside of the normal control flow, or other code that may be interrupted during maintenance.

Modern compilers will automatically place variables into registers. Use registers with caution for the variables you think are the most critical. In extreme cases, mark 2-4 most critical values ​​with registers and mark the remaining as REGISTER. The latter can #define as a register on machines with more registers.

19. Lint

Lint is a C program inspection tool used to check C language source code files, detect and report situations such as type incompatibility, inconsistent function definitions and calls, and potential bugs. It is highly recommended to use lint tools on all programs and it is expected that most projects will use lint as part of the official acceptance procedure.

It should be noted that the best way to use lint is not to use lint as a fence that must be crossed before official acceptance, but as a tool to use after a code is added or changed. Lint can find some hidden bugs and ensure the portability of the program before the problem occurs. Many of the information generated by lint does suggest that something is wrong. An interesting story is about a program that misses one parameter of fprintf:

Copy the codeThe code is as follows:

fprintf ("Usage: foo -bar <file>\n");

The author has never had a problem. But whenever a normal user makes a mistake on the command line, the program will generate a core. Many versions of lint tools can find this problem.

Most lint options are worth learning from. Some options may give warnings on legitimate code, but they can also capture a lot of code that makes things happen. Note that '-p' can only check the consistency of function calls and types for one subset of the library. Therefore, in order to maximize coverage checks, the program should perform lint checks with –p and without –p.

Lint can also identify some special comments in the code. These comments can force lint to turn off warning output when it finds a problem, and can also be used as documentation for some special code.

20. Make

Another very useful tool is make. During development, make will only recompile modules that have changed after the last time make. It can also be used to automate other tasks. Some common conventions include:

Copy the codeThe code is as follows:

all
Execute the construction process of all binary files

clean
Delete all intermediate files

debug
Build a test binary or debug

depend
Create a transitive dependency relationship

install
Install binary files, libraries, etc.

deinstall
Cancel installation

mkcat
Installation Manual

lint
Run the lint tool

print/list
Make a copy of all source files

shar
Make a sharp file for all source files

spotless
Execute make clean and store the source code into the version control tool. Note: The Makefile will not be deleted, even if it is a source file.

source
Undo what spotless does.

tags
Run ctags (it is recommended to use the -t flag)

rdist
Distribute source code to other hosts


Check out this file from the version control system


In addition, the value used by Makefile (such as "CFLAGS") or the value used in the source code (such as "DEBUG") can also be defined through the command line.

21. Engineering-related standards

In addition to what is mentioned here, each independent project expects to establish additional standards. Here are some of the issues that each project management group needs to consider:

* What additional naming conventions need to be followed? In particular, those functional classifications for global data and systematic prefix conventions for structural or union member names are very useful.

* What kind of header file organization is suitable for engineering specific data architectures?

* What procedures should be established to review lint warnings? It is necessary to establish a tolerance consistent with the lint option to ensure that lint will not give warnings for some unimportant problems, but at the same time ensure that real bugs or inconsistencies are not hidden.

* If a project has built its own archive, it should plan to provide a lint library file to the system administrator. This lint library file allows lint tools to check compatibility with library functions.

* Which version control tool do you need to use?

22. Conclusion

Here is a set of standards for C programming styles. The most important points are:

* The rational use of blanks and comments allows us to clearly see the structure of the program through code layout. Use simple expressions, statements, and functions so that they can be easily understood.

* Remember that at some point in the future you or someone else will likely be asked to modify the code or have the code run on a different machine. The code is carefully written so that it can be ported to machines that are not yet certain. Localize your optimizations because these optimizations are often confusing and we are pessimistic about whether this optimization measure is suitable for other machines.

* Many style choices are subjective and arbitrary. Keeping code styles consistent is more important than following these absolute style rules (especially consistent with internal standards of the organization). Mixed styles are worse than any bad style.

Regardless of the standard adopted, it must be followed if it is considered useful. If you find it difficult to follow a certain standard, don't just ignore them, but make a decision after discussing with your local master or experienced programmers in your organization.

23. References

. Tague, C Language Portability, Sept 22, 1977. This document issued by department 8234 contains three memos by . Haight, . Glasser, and . Lyon dealing with style and portability.
. Johnson, Lint, a C Program Checker, Unix Supplementary Documents, November 1986.
. Mitze, The 3B/PDP-11 Swabbing Problem, Memorandum for File, 1273-770907.01MF, September 14, 1977.
. Elliott and . Pfeffer, 3B Processor Common Diagnostic Standards- Version 1, Memorandum for File, 5514-780330.01MF, March 30, 1978.
. Mitze, An Overview of C Compilation of Unix User Processes on the 3B, Memorandum for File, 5521-780329.02MF, March 29, 1978.
. Kernighan and . Ritchie, The C Programming Language, Prentice Hall 1978, Second Ed. 1988, ISBN 0-13-110362-8.
. Feldman, Make -- A Program for Maintaining Computer Programs, UNIXSupplementary Documents, November 1986.
Ian Darwin and Geoff Collyer, Can't Happen or / NOTREACHED / or Real Programs Dump Core, USENIX Association Winter Conference, Dallas 1985 Proceedings.
Brian W. Kernighan and P. J. Plauger The Elements of Programming Style. McGraw-Hill, 1974, Second Ed. 1978, ISBN 0-07-034-207-5.
J. E. Lapin Portable C and UNIX System Programming, Prentice Hall 1987, ISBN 0-13-686494-5.
Ian F. Darwin, Checking C Programs with lint, O'Reilly & Associates, 1989. ISBN 0-937175-30-7.
Andrew R. Koenig, C Traps and Pitfalls, Addison-Wesley, 1989. ISBN 0-201-17928-8.

Previous page123Read the full text