Copyright © 1993,1998 Eric Laroche
All rights reserved
The paper shows the abstractions and considerations that lead to recommendations in C programming. It is the author's opinion that all the coding recommendations can be deduced from general coding aims.
Note that software defects can be a security problem2.
Then, software engineering aims are
The importance3 of the software engineering issues mainly depends on the overall size of a project. If you intend to expand the project or to convert parts of it into a (reusable) software library, software engineering issues should be considered from start on.
We can say that the smaller the project, the more likely it is manageable without considering software engineering issues. However, if your design and coding experience allows, I recommend not to neglect software engineering issues even with the smallest projects, since
Design issues are not so much covered in this paper, since they are often independent of the programming language.
This paper won't compare the C programming language to other programming languages. Some aspects, like efficiency and economy of a programming language depend heavily on a programming environment's implementation6. Other aspects, like reliability (e.g. through pre- and postconditions) can be seen in the C language as part of the design rather than coding.
However, a few footnotes are included about better approaches of the C++ language in (not object orientated) language details.
There is an older standard K&R1 (Kernighan/Ritchie, 1st Edition 1978), also known as pre-ANSI-C.
Use the newer standard if possible. It does rigid type checking (and implicit casting if necessary) on function arguments (provided that function prototypes and appropriate header files are used).
Tools to convert from K&R1 to K&R2 or to add function prototypes are available (e.g. protoize).
You should limit yourself to the ASCII character set in C source files. The printable ASCII characters are represented by the bytes hexadecimal 20 to 7e.
Non-ASCII characters (e.g. ISO-Latin above hexadecimal 7f) are syntactically incorrect when used for identifier names (even if some compilers may accept such identifiers).
Comments, strings and character literals should not contain non-ASCII characters.
Non-ASCII characters and control characters (hexadecimal 0 to 1f, with the exception of newline, carriage return, tabulator and formfeed) may confuse editors or may even lead to unexpected results on some compilers (e.g. ^Z misinterpreted as end-of-file, most significant bit stripped on non-ASCII characters, etc.). There is no means of notation of the encoding type of a source file (unlike with mail messages).
C offers nice ASCII-notations for non-ASCII characters, e.g.
'\xe8'
.
To let the sources be globally readable, the lingua franca of computer science, english, should exclusively be used. As with other parts of the C coding guidelines: the smaller the scope context, the less strict the rule.
Customizing your program's user interface to a local language
(i.e. internationalization in the stricter sense) can be done with
locales and e.g. the gettext()
family of
functions.
These functions let you use compiled text databases for the different
ISO language codes, while leaving your C sources still readable, i.e.
(english) strings are still present in the code.
Note that internationalization covers also local formats for things like dates and money.
There are of course many styles of choosing identifier names. One important rule is to be consequent about naming.
The smaller the scope of a name, the less important a good choice of the name is. E.g. it is less important to chose a speaking and somewhat systematic name for a small scope index variable8 than it is for an exported library function.
Using capable development environments (the ones that let you jump to definition and declaration of a function) makes it less important to express type and package membership in a function name.
The aspect of which natural language to use for names has already been covered.
get_name
)GetName
)getname
)Some software packages use a kind of type prefix (e.g. uppercase initial for functions and lowercase type indicating initials for variables). E.g.: sz (zero terminated string), n/i (integer), p (pointer), psz (zero terminated string pointer), f/b (flag, boolean), c (character), fp (file pointer), pfn (function pointer), h (handle), w (word, unsigned int), fd (file descriptor).
Package prefixes (e.g. t_ in the TLI API) will be mentioned below.
_
or __
are
reserved for the C implementation and may not be used.str
(string functions, string.h
),
E
(operating system error numbers,
errno.h
).
Libraries tend to include package membership information (e.g.
t_open
, t_bind
, etc. of the TLI network
interface library) to omit name collisions12.
However, it will be impossible to find small unique package prefixes13.
Filesystems (in which the source files are stored) may or may not be
case-sensitive14.
In the later case, one cannot save e.g. Main.c
and
main.c
in the same directory.
The recommended character set for file names, valid on most
systems, is rather small: letters, digits, .-_
and
maybe ~@+%,
, reasons being
/\:;
)"'\`
)<>()[]{}$=`;&|^*?#!
)
A package prefix is considerable with header files to avoid namespace
problems (a bad example being external header files named
error.h
or types.h
).
Note that there may exist differences in the number of warnings issued
between an non-optimizing compile (cc -O0
...) and an
optimizing compile (cc -O3
...).
Of course both compiles should be satisfied.
Further, extended syntax-check programs such as lint should be used permanently.
The time saved by using the compiler's hints is typically more than the time spent to keep the sources warning-less.
Some third party libraries or even compilation systems(!) may include header files that produce annoying warnings when compiled with the highest warning level. I recommend to encapsulate the offending interface declarations in a code layer that meets the warning-less requirement
If the source code is to be distributed and will be used on a variety of systems, it should be revised for warning-less compile on several compilers and/or computer/operating systems. You may also want to do cross-compiles.
Compiler warnings may be a hint of incompatibilities. E.g. the warning "possible bad alignment" may not be an error on CISC CPU architecture systems, but will be one on RISC systems.
Compiling C code as C++ code may reveal C/C++ incompatibilities15, specially with assignments from and to void*
16.
The warning "assignment in conditional expression" can be avoided by
using while((p = next()) != 0)
instead of
while(p = next())
.
The warning "conditional expression is constant" can be avoided by using
for(;;)
instead of while(1)
.
The "unreferenced parameter" warning is a tough one in the C language17. Suggestions were: casting to void (does not suppress the warning on all compilers) and self assignment (looks weird and lets me think of a "statement has no effect" warning).
Asserts (assert.h
) may produce "statement has no
effect" warnings in the release version.
This one seems to be tough.
The "comparison of signed and unsigned values" warning appears if signed
and unsigned values are mixed in a less/greater expression.
One approach is not to use unsigned values at all.
strlen()
may unfortunately require an int
cast.
lint
is a traditional Unix development tool,
originally designed for K&R1.
It allows
char p[]
vs.
extern char* p
)printf()
or
scanf()
Simple metrics count e.g. the
Advanced metrics can count function points, express code complexity (e.g. statements per function, nesting, number of cross-references, etc.).
Even more advanced metrics may try to track changes in micro architecture19, changes in interfaces and the like, as the project development goes on. This may be accomplished together with configuration management tools.
Assertions can be used to check the internal program logic and a correct program flow.
Drawbacks are:
Release versions typically do not contain the assertions. Beta versions may or may not contain the assertions.
The use of such tools may be time consuming since the program may run factors slower. The tool may not be able to distinguish between e.g. memory leaks (bad) and reusable buffers that won't be freed (a valid design decision).
Samples are: purify, electric fence (dynamic memory checks only).
These applications may be used in automated testing suite.
You should not avoid using temporary variables in order to enhance performance. Temporary variables can be optimized away to lead to the same code that was expected from the complicated expression (if the compiler is powerful enough).
One drawback of nested expression was the warning "assignment in
conditional expression" with if(p = malloc(size))
.
The warning can be avoided by using parentheses or by separating
statements: p = malloc(size); if(p)
.
This may additionally make debugging easier.
Redundant code is harder to maintain and increases the probability of introducing defects. Code with many redundancies is harder to read.
You might consider implementing a more general function instead21.
If performance is the problem, use macros or an optimizing compiler that inlines the functions (preferred).
Searching for bugs that show non-deterministic symptoms is an unpleasant task at best.
You may consider to assign a freed pointer 022.
No extern declaration must appear in C files. They belong in commonly included header files.
I tend to design one header file for each C source file. If several modules are linked together to a library, a new external header file may be created that declares all extern functions. Alternatively, all of the extern functionality can be encapsulated in one source file25. Its according header then becomes the external header.
There are very few reasons to include static definitions in header files.
There is in my opinion no reason to include static function declarations in header files. They belong in the source file that implements the functions.
#include "header.h"
, header files that are extern to
the component should be included with
#include <header.h>
.
I tend to prefer the extern include, if a header file can be seen to
declare stuff that's independent of the component, despite the fact that
usually -I.
compiler options are necessary to indicate
where to find the header files.
Header files must be included by both the implementing module and the calling module(s). Note that the inclusion in the implementing module is not enforced by the compiler and the linker will link independently of actual function parameter types26.
As mentioned above, it's wrong to declare static functions in a header file or to not define functions as static that are only used in one module.
Header files should use include guards to enable27 (intended or unintended) multiple inclusion28:
#ifndef header_h
#define header_h
#endif
Header files must not be included by an absolute path (e.g.
/usr/include/header.h
), paths can be specified in compiler
options (-L/usr/include
).
Using relative paths is correct (e.g. sys/header.h
),
however using ..
seems confusing (e.g.
../header.h
).
Never use the extern
keyword in source files, use it only
in header files.
Resource data should carry the const
modifier, so it can
possibly be put into a read-only data section29.
This allows instances to share the resource memory and the operating
system does not need to allocate virtual memory paging space for it.
There are some aspects to consider about the order of the code in a source file:
Error handling should typically be done without delay, e.g.
fp = fopen(file, "r");
if(fp == 0) return -1;
process(fp);
fp = fopen(file, "r");
if(fp != 0) process(fp);
else return -1;
Nested conditional compiles seem to confuse further.
However, suitable editors can display uncompiled parts of the code in comment font type31 and even fold it, to make sources more readable. There are tools available to remove the unused conditional parts32.
Alternatives to conditional compiling are including one of different
header files (with the -I
compiler option) or linking
one of different libraries (with the -L
compiler option) or
both.
In development, you may chose to create a set of subprojects and include
and link one of them.
Conditional use of parts of code, header files, libraries or subprojects is often used to encapsulate platform specifics.
You may want to design a separate local function that implements the inner part of a loop, to avoid deep nesting. This may make it easier for a compiler to optimize the code33.
I would say that no more than two levels of nesting should be used.
Designing small functions and using return
on error cases
instead of adding a layer of if
/else
further
reduces nesting and makes code more readable (in my opinion).
Choosing the scope of a function, variable or type is one of the most important micro architecture instruments.
Generally, scope should be chosen as narrow as possible.
static
) or application scope
(not static
).
Limit a function to file scope if possible.
This affects the modularization of a software component (i.e.
the way functions are grouped together to source files).
A narrow function scope encapsulates code.34
Typically, there is no overhead (stack pointer operations) involved with block scope variables, space for the deepest possible block allocation gets reserved at function entry.
Application scope leads to global variables, which generally
should be avoided.
Use functions to access the data (getX()
and
setX()
) instead36.
Application scope or file scope (as well as static data in functions) lets the functions that access the data only be useable by a single thread37.
A reference to a calling functions buffer should be used instead of
global or static data to avoid multithreading problems and buffer
overwrite problems (e.g. as with localtime()
).
Don't redefine types in file scope, use common header files instead.
However, macro encapsulating code fragments as
#undef m
#define m
...
#undef m
Limiting macro scope is a bit obscure.
As a sample, Java doesn't pose problems with explicit casts, array sizes, buffer sizes, macros and less problems with error checking (through the use of exceptions). Lisp e.g. doesn't pose problems with number ranges.
Use explicit casts as rarely as possible39. It's good to think whether an explicit casts is necessary and what the compiler will do with it.
The C language allows implicit conversions from T*
to
void*
and vice versa40.
There is no explicit cast needed in C to convert from
void*
.
malloc()
is a sample of an often used function that returns
void*
.
int
size limits the range of numbers you can use41.
Check if e.g. 31 bit (signed int
) is enough for your
problem domain.
int a[32];
):
32
SIZE
(a macro used for the array definition)sizeof(a)/sizeof(*a)
This rule should be strictly followed if the input size is an external
(and hence uncontrolled) property (e.g. a line length with
gets()
).
Buffer overflows can
malloc()
or
free()
.
In that case often only a malloc debug package helps.
A sample of a corrupted stack frame is a program that crashed (on a little endian system), leaving a core42. Because of the overwritten stack, the debugger that was used to examine the core (sdb) was unable to display a backtrace and just displayed the message "cannot get_text from 0x63697245", which confused on the first look, but was a good hint that upon returning from the corrupting function, the program tried to jump to nil.
One problem is to guess how much buffer size a sprintf()
will require.
However, sprintf()
allows to specify maximal lengths of
spliced fields to limit the output string size (e.g.
sprintf(buf, "
...%.*s
...", sizeof(buf)-1-
..., p)
.
Note that buffer overflows are security problems. Overwriting stack based buffers (while knowing the affected program and the system it runs on very well) can be used to insert manipulated function caller addresses and hence execute malicious code43.
#define sqr(x) x*x
sqr(a+b)
#define sqr(x) ((x)*(x))
sqr(a+b)
#define max(a,b) ((a)>(b)?(a):(b))
k = max(i++,j++);
A typical sample is the sign extension from character to integer:
char* p;
...
printf("0x%02x", *p);
0xffffffe4
than an intended
0xe4
.
The character needs a cast to unsigned before the conversion:
printf("0x%02x", (unsigned char)*p);
Lint's warning "return value sometimes ignored" may help to identify offending code locations.
A classical C language programming error is not to check
malloc()
for nullpointer return44.
*p++ = *p++ = 0;
*p = 0;
p++;
*p = 0;
p++;
*p = 0;
*p = 0;
p++;
p++;
It would be nice if undefined behavior through missing sequence point definition was generally diagnosed by compilers45.
There are two considerations:
0
,
1
and -1
.
Define the numbers as constants, macros46 or enums outside the functions.
They should especially not appear in the code if they're meaningful for limitations or performance of an algorithm (e.g. if they limit some input size).
Counterexamples are
Use as few hardcoded values as possible. Don't use static sized tables of data, since they are almost never appropriate.
Don't generate any hidden dependencies among constants. Define constants by means of the constants they derive from.
Numeric constants are hard to understand if they're at the same time not commented and not composed of other named constants. Compilers are quite able to do arithmetics at compile time, use them.
Sample: if you need a buffer to hold a string representation of an
integer, define its size in terms of INT_MAX
or
sizeof(int)
.
E.g.: sizeof(int)*5/2+3
47 (assuming 8 bits per byte).
Typically you will only gain one of 32 or 64 bits, which can often be neglected. Again: know your problem domain. If you need more than 31 bits in an application, you may want to switch to 63 bits or bignums.
The C language will also not indicate an exception if you subtract a larger unsigned number from a smaller unsigned number, so you can't make your programs more robust by means of using unsigned values.
Using signed and unsigned values leads to ambiguities when comparing or adding them.
Traditionally, long
and short
(or
unsigned long
, unsigned short
) were used
(together with htonl()
, ntohl()
,
htons()
, ntohs()
) in implementing low-level
network protocols, such as UDP-based application protocols50.
The assumption was that C implementations define a long
to
be exactly 32 bits, which is however not defined by the C language
standard.
Use ASCII representations of numbers, when you write them to file or network, in order to be system architecture independent (size, byteorder, padding)51.
Besides using htonl()
and ASCII, there exist some
architecture independent data representation libraries like
XDR52.
However, ASCII representations seem easier to debug, because human
readable.
Using shorts may save significant space in large arrays. However, if the problem domain changes, shorts may become too small. Conversions from shorts to ints and back may also bring some computational overhead.
Know also that unexpected alignments may occur if you mix
shorts and longs.
Sample: struct {short a; long b;};
will most probably be
eight bytes of size, not six53.
double
,
float
) if possible.
Reasons being
Many problems are solvable without using floats.
E.g. a typical hashtable high-water-mark of 0.75
may be
expressed by a ratio and handled by integer arithmetics:
if(4*items > 3*size)
...
Avoid single precision float
.
Use double
.
If you have to deal with single precision floats on file, then encapsulate the code that deserializes (reads them back).
If space counts, you may consider to use normalized numbers, that are adapted to the problem domain (e.g. shorts signifying 1000th).
Use int main(int argc, char** argv)
instead of
int main(int argc, char* argv[])
.
The internal semantics of a parameter are that of a variable declared as
char** argv
, not char* argv[]
.
Some compilers (and e.g. lint) warn of wrong arguments supplied to the
variable argument function families printf()
and
scanf()
, which are part of the standard C library.
short
to long
),
then introduce a type synonym for this type using typedef
.
Some standard library functions might even get inline expanded
(memcpy()
), so there's probably no performance problem.
You should use stuff that's offered.
E.g. strerror()
will tell a lot about the origin of an
error reported by the operating system.
Not using it will leave the user and support group clueless.
Don't use gets()
and the scanf()
family for
safety (buffer overflow crashes or program corruption) and
security reasons (buffer overflow exploits).
Use fgets()
respectively
fgets();strtok();atoi();
etc. instead.
if(!p)
...if(p == 0)
...if(p == NULL)
...register
.
One could assume that compilers know the CPU registers better than the C programmer does, since they are the interfaces to the register-using assembly languages.
Also, compilers are free to ignore the register
keyword
(and often will57).
auto
.
Rarely, auto
was used to emphasize that a function
variable needs explicitly to be automatic.
E.g. in a recursive function in which some variables may be modified to
be static (to save stack space).
However, the latter is bad practice since it is not multithreading
save58.
goto
.
Gotos lead to a confusing program flow.
Most of the control flow problems can be solved by using additional
layers of local functions (that need not imply overhead).
Use return
to jump out of them.
Introducing function layers may enhance modularity and code
encapsulation.
Appreciate also break
and continue
instead of
goto
.
return
statements
in a function.
I see multiple returns as a good micro design construct. They allow function code to be less deeply nested.
&&
and
||
as standalone statements.
Don't use
f() && g();
if(f()) g();
Don't overuse the comma operator.
?:
59long
and casting all possible arguments to
it)However, independently of the indent style you use,
Not all editors can preserve tabs or blanks64. In a worse case, only the indentation of changed lines in a source file is converted.
Tabulators can also become victims of branch merge tools (which are part of revision control software).
{}
) can either appear on a line
of their own or on the preceding line.
The closing braces being right after the last statement (Lisp style)
being rarely seen.
If the opening and/or closing braces are on a line of their own, they can be adjusted to the indent level of the outer block or to that of the inner block or (halfway) in between.
The use of the above styles can differ between code (functions) and data (structs, unions, array initializations) and can differ between top level code braces (functions) and function level code braces (do, else, for, if, switch, while).
Braces may or may not be omitted in control blocks if the block covers one or zero statements65.
case
, default
) can either
appear adjusted to the outer block indent level or to the inner (with or
without adding one more indent level for the code in the switch
statement66) or in between.
Goto labels can be adjusted to the left margin (i.e. top level block), one indent level less than the next statement, or on the same level as the next statement. The first two styles are more readable.
Be consequent about placing the labels.
else if
' and
'int i
').
However, lots of blanks are typically used to make source code more
readable.
Be consequent about using blanks between tokens.
Trailing blanks and tabulators change only the meaning of trailing backslashes (e.g. in C++ style singleline comments67 where they're legal and have a semantic). Using trailing blanks and tabulators in this context is dangerous, since editors may be inaccurate in preserving them.
You might include some evident information in every C source file:
Don't comment the obvious.
Don't use comments as substitutes for speaking identifier names.
Comments can have the semantics of a tool directive (e.g.
/*LINTED*/
, /*ARGSUSED*/
,
/*NOTREACHED*/
or /*EMPTY*/
for Lint).
They might or might not be used.
You may consider to put an empty line between blocks to make them a bit more readable. You may generally consider consequent spacing.
In the C language (unlike the C++ language69), a block has two parts: a variable definition part and a code part. These two parts should be separated by a blank line to make them more readable.
I would not recommend to comment the closing brace of a block.
A suited editor typically lets the user jump to the corresponding
opening brace.
The same applies for #endif
.
I hope to have given a thorough insight into most aspects70 that are essential in establishing C programming language guidelines.
1 I know, there are no bugfree programs, at least if they are large enough (like sendmail), they can't be bugfree.
2 sendmail again. The security problem won't be treated any more in this paper.
3 A software engineering biased quote: If it doesn't work, it can be fixed. If it can't be maintained, it's scrap. (Well, it may not work and can't be maintained either...)
4 Readable for a human, not for the compiler.
5 However, I put no recommendations where the choice is totally yours, e.g. chosing one of the much discussed indent styles.
6 Samples of good efficiency of other programming languages are the ability of Lisp environments to compile their functions and the possibility to post-compile Java virtual machine bytecode into native binary code.
7 K&R2 [BK/DR,1988] is based on the ANSI C draft X3J11/88-001 and was published before the ANSI standard ANS X3.159-1989. This standard was adopted as ISO/IEC 9899:1990 and is also known as ANSI/ISO 9899-1990. Note that ANSI and ISO require periodic reviews of their standards.
8 However, I find names as n or i quite speaking for local index variables. At least if the loops are kept small, as they should (for good modularity).
9 Underscores may not appear as such in a view that underlines identifier names (e.g. identifiers displayed as HTML hyperlinks). They appear to be blanks.
10 Considering the (historic) limitation of external identifier names to only six characters seems not feasible today. Not many good identifier names would result if you were to use only six characters or had to avoid the reuse of the same six starting characters.
11 The Java namespace model is based on (possibly large) hierarchical names, proposing internet domains as a base. Packages are named like com.sun.applet. This is a thought-out approach to get unique names.
12 One may ask if it was usual to provide only lean interfaces (only few functions) in C libraries partly because of the missing package support, and hence the possible name clashes with many names.
13 One of the reasons C is not specially suited for large software bases.
14 Note that I make a distinction between case-sensitive and case-preserving. The later doesn't allow you to save file names that only differ by case, although the case will be preserved.
15 If you intend to develop hybrid C/C++ code (not recommended).
16 Which will produce errors under C++. That's why casts are still seen with malloc() calls (in C/C++ hybrid code).
17 In C++, the name (but not the type) of the unused parameter can be left away. Nice solution.
18 Unlike the quantity, the quality of the comments can't easily be measured with metrics. However, it will be revealed by (human) code reviews.
19 Of course such things are more important if the software project is a large one.
20 Be sure however to have an operator precedence table at hand all the time.
21 Although abstract and generic programming is not so much supported as it is in C++.
22 Functions may check pointers for 0 and issue understandable diagnostics (instead of crash, e.g. printf() writing <null>).
23 The (object oriented) C++ class concept and inheritance decrease complexity even further.
24 To be avoided.
25 Note that if you want to link only the functions of your interface that are really used, and your linker can't throw unused functions out of an object file, you have to put each independent function in its own module and use an external header file.
26 Not so in C++. What was designed to allow function overloading will also do type checks in the link phase.
27 C does not allow type and macro redefinitions, even with the same definitions. Also, the include guards make it easier to allow circular header references (though not recommended).
28 To provide an interface that is open to C and C++, you may also want to declare C linkage in a similar way: #ifdef __cplusplus extern "C" { #endif ... #ifdef __cplusplus } #endif.
29 By the linker respectively the loader.
30 These styles obviously must be mixed when using indirect recursive functions that are implemented in the same module.
31 The editor needs to know about the defined macros then, obviously.
32 The C preprocessor usually can't do this, since it will also expand included header files and macros.
33 Compilers/optimizers tend to get confused if too many variables are present and give up optimization.
34 The C++ language offers additional encapsulation mechanisms with its class concept. By specifying members (data or functions) to be private (or protected), C++ can limit scope to certain (class) functions.
35 One may argue whether to insert generic blocks (not part of a function, loop or similar) to do additional variable scope narrowing. It can encapsulate variables in large flat legacy code, but it does not seem to be a good design decision.
36 However, using explicit access functions can be seen as a design flaw because modules should provide functionality, not plain data access.
37 If a monitor is used, the static data will be no problem. However, still no reference on the static data can be returned to multiple threads.
38 C++ introduced two less powerful casts to defuse the situation. (One to change a const specifier only and one to cast from one pointer type to another only.)
39 [BS,1997] sees casts (though in C++) (in most cases) as in indication of a design error.
40 C++ does not. This is one of the more obvious incompatibilities between C++ and C.
41 And C comes, unlike Lisp, not with a standard bignum (only resource-limited size) numeric type.
42 A process image (data and stack memory) that can be used for post mortem analysis of a program crash (on Unix).
43 Specially dangerous with (network) services and even more if they run with high operating system privileges.
44 C++ handles its memory allocation (operator new) with a bad_alloc exception and needs not be handled.
45 Lint may give you the warning "evaluation order undefined: p".
46 Unlike C++, C does not allow const int values to be used as array sizes. A macro definition must be used instead.
47 5/2 as upper approximation of 8*log10(2), with additional bytes for integer division truncation, sign and string termination. Don't forget to give this constant a meaningful name.
48 I'm not talking of implementing something like virtual memory support as part of operating system code here.
49 However since the memory layout is probably different on a 16 bit system (can't allocate large data chunks, must partitionate them), int/long portability will not be the only problem.
50 Of course also in operating system code; but in that context you can less say that the code should not assume of what size an int is.
51 This applies also to floating point numbers.
52 XDR (external data representation) e.g. used by RPC (remote procedure call) e.g. used by NFS (network file system).
53 The same happens with struct {long b; short a;}, to get the longs aligned in an array of such structs. The padding will in this case be at the end of the struct and not create a gap between the struct members.
54 These floating point handling routines may be quite large on operating systems that support processors that lack a floating point unit. Floating point logic may not be implemented on the operating system level on such systems (small embedded systems kernels being a sample).
55 Variable arguments are discouraged by C++. The C++ alternative is to provide different, overloaded functions. Calls to printf() are replaced by calls to ostream::operator<<().
56 C++ tends to absolutely not use the NULL macro.
57 Compilers must ignore at least some register keywords anyway if the programmer specifies too many.
58 Also, truly recursive functions (e.g. qsort) can be designed to descend the smaller branch(es) of the recursion tree first and take the last branch iteratively. This will lead to only small stack space requirements.
59 Users of functional programming languages may be pro-biased on this one.
60 However, two quotes (couldn't resist): Don't optimize early. When speed and space are an issue, style guides are thrown out the window (however, I do not totally agree with the last one).
61 I think modules should rather represent logical units than be limited in any physical form (e.g. in the number of lines of code).
62 These indent-style filters could even be triggered by revision control software upon check-out and check-in to let the programmer have its own style. However, this requires very consequent use of revision control tools, which may pose some unwanted overhead.
63 A special annoying style is an indentation with four blanks where every pair of four blanks is converted to one tab. This looks like half of the indent levels (2, 4, ...) are left out when viewed with a tabstop setting of four (instead of eight).
64 You may question the quality of such editors. They shouldn't be used for coding.
65 Note that a closing brace may serve as an anchor for debuggers. You may not be able to jump out of an unbraced loop in source debugging mode.
66 I do not recommend adding additional indent levels.
67 Not part of the C language, but tolerated by many C compilers.
68 C++ much more encourages mixed lines with code and comments because of its singleline comments construct.
69 In C++ a variable definition can appear anywhere a code statement can. This allows always immediate initialization.
70 Of course, there's always more.
[BK/DR,1988] The C Programming Language, B. W. Kernighan, D. M Ritchie, Prentice Hall, 2nd Ed. 1988
[BS,1997] The C++ Programming Language, B. Stroustrup, Addison-Wesley, 3rd Ed. 1997
Keywords: C programming language coding guidelines, C coding guidelines, C coding standards, C rules, C recommendations, coding in C