Ethernut Home Hardware Firmware Tools Download Community
 
 
Search | Legals | Deutsch

Nut/OS Programming Style Guide

by Harald Kipp

This guide is intended for people who are (or intending to start) writing code for inclusion into the Nut/OS distribution. It is separated into two parts: The first one addresses the general compatibility issues and is not Nut/OS-specific. The advices in this part will hopefully help you to write programs which compile and run on greater variety of platforms. The second part details the Nut/OS code organization and its goal it to make Nut/OS as uniform as possible without imposing too many restrictions on the programmer.

Acknowledgements

The basis for the following text had been taken from the  wxWidgets Project , with kind permission from Vadim Zeitlin. Vadim's text contains the following acknowledgement: This guide is partly based on C++ Portability Guide by David Williams.

I adapted it to Nut/OS. For errors and inconsistencies blame me, not the original authors.

General C Rules

New or not widely supported language features

The usage of all features in this section is not recommended for one reason: They appeared in C relatively recently and are not yet supported by all compilers. Moreover, when they're supported, there are differences between different vendor's implementations. It's understandable that you might love one (or all) of these features, but you surely can write C programs without them.

In the past, Nut/OS tried to support compilers, which do not fully support C89 or implement incompatible features. This is no longer the case.

The lowest common denominator is C89

Be aware, that many embedded systems and their related tools have a much longer lifetime than personal computers. In that sense the term recently may refer to a few decades. Furthermore, it requires a lot of effort to implement a full featured C99 or C11 toolchain for a completely new CPU design. Often, hardware vendors are able to offer a limited compiler implementation only. It's a big advantage of Nut/OS, that doesn't require more than a simple C89 toolchain.

Using extensions
In many cases the pure C89 standard is not able to support specific requirements of a specific hardware platform. In such cases, using compiler extensions is unavoidable. Limit these extensions to files, that are compiled for this specific platform. Whenever possible, even in architecture- specific files, try to use standard C89, so others may still be able to use at least a part of your code.

If you must use extensions to the C89 standard, prefer newer C standards over vendor specific implementations. For example, C89 doesn't provide types having specified widths, while C99 offers uint8_t, uint16_t etc. Use these instead of u08, u16 or similar types often seen in hardware vendor supplied libraries. Such extensions are most welcome, because they do not rely on compiler features, but simply require a few typedefs in a new header file. Btw. Nut/OS comes with a stdint header file to implement these types...

...as long as the base types are supported by the compiler. Many embedded platforms do not support integer types of more than 64 bits. Therefore, types such as long long are banned from the target independent part of Nut/OS. Of course you can (and often have to) use them in your architecture dependent code.

Note, that we used stdint here as an example only. It is expected, that the upcoming C11 standard will cover some embedded system requirements. When defining new types or macros to emulate new feature, try to make them compatible to newer C standards.

Avoid string concatenation in global preprocessor macros
By nature, partial Nut/OS code is included or excluded during compile time, using configurable preprocessor macros. There may be cases, where an advanced hardware layout requires complicated and hardly maintainable macros. In such cases, the string concatenation feature ## of the preprocessor comes in quite handy. Use it, if it helps to keep your code more readable and maintainable. But make sure, that it is supported by all compilers of the related architecture. If not, there is no way to emulate this feature and compilation will fail. Never force users to use a specific toolchain just to make the code pleasing your eyes.

No C++/C99 comments in our C code
Never use double slashed comments in C code -- not all C compilers/preprocessors understand them.

Start preprocessor directives in the first column
Some C preprocessors require that all directives start in the first column, so you should start them at the beginning of the line.

Other compiler limitations

This section lists the less obvious limitations of the current C compilers which may be more dangerous as a program which compiles perfectly well on some platform and seems to use only standard C featurs may still fail to compile on another platform and/or with another compiler.

Why CONST instead of const?
A few C compilers for embedded systems, most notable ImageCraft, used the const keyword for specifying data in program memory, when creating code for Harvard architectures.

This is quite annoying, because the const keyword is used by the majority of compilers for optimization purposes, specifically as an attribute of function arguments. After ImageCraft finally understood the problem, they moved to the standard in version 7 of their compiler. However, changing the Nut/OS code back is not that easy and you may still find a few references after support of ImageCraft version 6 had been dropped.

Why INLINE instead of inline?
This is used for similar reasons as with CONST, but support on ImageCraft is still missing. However, the inline keyword is currently over-used in Nut/OS anyway and more has to be done here.

In Nut/OS version 5 new macros had been introduced, which are prefixed by NUT_ to avoid name clashes with existing libraries. INLINE should be replaced by NUT_INLINE_FUNC.

Don't use extra semicolons at top level
Some compilers don't pay any attention to extra semicolons at top level, as in

int Foo(void);;
while others complain loudly about it. Of course, you would rarely put 2 semicolons yourself, but it may happen if you're using a macro which already has a ';' inside and put another one after it.

Terminate the files with an end-of-line
While many compilers don't seem to mind, some don't like files without terminating the last line. Such files also give a warning message when loaded to some editors, so please think about terminating the last line.

Avoid globals differing by case only
This is not directly related to the compiler itself, but to related tools. Not all of them may be case-sensitive. Therefore all external variables and functions which differ only in case may not recognized as different, so all externals should differ in more than the case only.

General recommendations

This section contains compiler and platform independent advice which must be followed if you wish to write correct, i.e. working, programs.

Turn on all warnings and eradicate them
Give the compiler a chance to help you -- turn on all warnings! You should always use the maximum available warning level of your compiler and understand and correct each of them. If, for whatever reasons, a compiler gives a warning on some perfectly legal line of code and you can't change it, please insert a comment indicating it in the code. Most oftenly, however, all compiler warnings may be avoided (not suppressed) with minimal changes to your code.

Don't rely on fixed data type sizes
You should never assume any absolute constraints on data type sizes. Currently, we have 8-bit and 32-bit targets and sizes are different. A small table illustrates it:

Architecture sizeof(short) sizeof(int) sizeof(long) sizeof(void *)
8-bit AVR 2 2 4 2 or 3
32-bit ARM 2 4 4 4
VSDSP* 1 1 2 1 or 2

* VSDSP is currently not supported, but included here to show, what the C standard allows.

No assignments in conditional expressions
Although close to the heart of many C programmers (I plead guilty), code like classical

if ((c = getchar()) != EOF)
is bad because it prevents you from enabling "assignment in conditional expression" warning, which is helpful to detect common mistypes like if (x = 2) instead of if (x == 2).

Use #if 0 rather than comments to temporarily disable code
If you have to temporarily disable some code, use

#if 0
...
#endif
instead of
/*
...
*/
The reason is simple: If there are any /* ... */ comments inside ... the second version will, of course, miserably fail.

Development environment differences

Two development environments are supported by Nut/OS right now, different flavours of Unix and Windows. The main differences between them are summarized here.

Don't use backslash in #includes
Although it's too silly to mention, please don't use backslashes in #include preprocessor statement. Even not all cross compilers running on Windows accept it, without speaking about all other ones.

Avoid carriage returns when passing code
This problem will hopefully not arise at all, with CVS taking care of this stuff. However, when passing code by download, file shares or email attachments from one environment to another, it may happen that carriage returns make their way in to the repository.

Use all lower case filenames
Host file systems may be case preserving, but not case sensitive. To avoid all kinds of problems with compiling under any other OS with a fully case-sensitive file system, please use only lower case letters in the filenames.

This recommendation is valid for C source code and header files. For other files, different rules may apply. By convention, the name of the Makefile starts with a capital letter and the name of any README file contains all upper case letters. The reason behind is, that names starting with uppercase letters will appear first in a sorted directory listing. Furthermore, the case of the extension 'S' or 's' in GNU assembler filenames controls, whether the preprocessor is invoked or not.

Style choices

All Nut/OS specific style guidelines are specified in the next section, here are the choices which are not completely arbitrary, but have some deeper and not Nut/OS-specific meaning.

Don't use the const attribute for non pointer arguments
In C an argument passed by value cannot be modified - or, more precisely, if it is modified in the called function, only the local copy is really changed, not the caller's variable. So, semantically speaking, there is no difference between void Foo(int) and void Foo(const int). However, the const keyword is confusing here, adds nothing to the code.

Use NULL or '\0' rather than 0
We have chosen to use the standard NULL macro in the expressions involving the pointers instead of just the constant 0. Although both are perfectly equivalent in this context, we feel that using NULL provides better readability and stands out better.

A related advice is to use '\0' instead of 0 in the expressions involving character types.

Don't initialize static and global variables to zero
If not explicitly initialized, static and global variables are automatically initialized to 0 by the C runtime startup. Explicitly doing so rather makes sense, but may increase the size of the final binary image, which includes all initialization data.

Use include guards
To minimize the compile time C programmers often use so called include guards. For example, in the header file include/dev/foo.h you might have

#ifndef _DEV_FOO_H_
#define _DEV_FOO_H_

... all header contents ...

#endif
In this way, the header will only be included once for the compilation of any source file.

Nut/OS Rules

File location and naming conventions

File locations
The Nut/OS files for each library have their own subdirectories. So, for example, there is "nut/dev" for libnutdev.a or "nut/net" for libnutnet.a. There are also two special subdirectories called "arch" and "include". The arch subdirectory contains the files which are platform dependent and the include subdirectory contains all global header files.

Use angled brackets when including global header files
Note that source files should use angled brackets when including Nut/OS headers, not quotes. This allows full control of include paths by compiler options.

File layout and indentation

Use K&R coding style
At least four different coding styles are widely used, each of them with good arguments for and against. We decided not to discuss the pros and cons, but simply select one and use it consistently. Up to a certain extend, the Nut/OS coding convention follows the rules used in the Linux kernel , but in detail there a significant differences. In any case it is highly recommended to have at least a short look to the Linux document too.

Indent your code with 4 spaces, do not use tabs
This makes sure, that the code appears same on all environments. Tab stops are set on every 8th character, even if text editors allow to mimic other values. Setting tabs at 4 or 2 characters is wrong, or at least highly incompatible with existing standards.

On the other hand, using an indentation of 8 characters will move the code too far to the right and limit the nesting level. Using an indention of 2 only will make the code harder to read. Thus, 4 is a good compromise.

When using GNU indent, the following options create a Nut/OS conformant programming style.

indent -kr -nut -l 132

Note: Not everyone is happy with the maximum length of 132. After further discussions it may be reduced to 127 or even 79.

Switch statements
Align the keyword switch and its related case statements in the following way:

switch (i) {
case 0:
case 1:
    NutSleep(100);
    /* Fall through. */
case 2:
    NutSleep(100);
    break;
default:
    break;
}

Spaces and operators
Add a single space on each side of binary and ternary operators. Do not use spaces after unary operators, structure member operators or prefix operators or before postfix operators.

/* This is bad style. */
i=k+1;
j = ! i;
if (jk) {
    k -> test = -- a&b ++;
}

/* This is fine. */
i = k + 1;
j = !i;
if (j < i && j > k) {
    k->test = --a & b++;
}

Spaces and parentheses
Add a space after specific keywords only: if, switch, case, for, do and while.

/* This is bad style. */
do {
    CallMe ("Bond");
} while(athome);

/* This is fine. */
do {
    CallMe("Bond");
} while (athome);

Do not add a space after opening or before closing parentheses.

/* This is bad style. */
calculate( 1 + 2 );

/* This is fine. */
calculate(1 + 2);

Hidden spaces
Do not leave trailing spaces at line ends. Some editors will automatically remove them, which will later result in large and weird patches.

Pointer declarations
The '*' shall be adjacent to the variable or function name and not to the type name.

/* This is bad style. */
int* where(char* hidden);

/* This is fine. */
int *where(char *hidden);

Only one statement per line
Keep everything as simple as possible.

/* This is bad style. */
if (at_home) CallMe();

/* This is fine. */
if (at_home) {
    CallMe();
}

The same applies to multiple assignments.

/* This is bad style. */
i = j = k = 0;

/* This is fine. */
i = 0;
j = 0;
k = 0;

Always use curly braces in conditional or loop statements
If a conditional or loop statement is followed by a single statement, curly braces are not required by the language. However, adding statements later is much easier if the braces are already there. This will also make diffs more readable.

/* This is bad style. */
if (at_home) {
    CallMe();
    Talk();
}
else
    DriveHome();

/* This is fine. */
if (at_home) {
    CallMe();
    Talk();
} else {
    DriveHome();
}

Reduce #ifdefs to a minimum
Too many #ifdefs make code unreadable and unmaintainable. See '#ifdef Considered Harmful'.

Nut/OS copyright header
All Nut/OS source files should start with the following standard header

/*
 * Copyright <year> by <copyright holder>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * For additional information see http://www.ethernut.de/
 */

For the initial release replace <year> by the year the code had been created. This will be expanded, when the code is modified in the following years. Replace <copyright holder> by, you guessed it, the name of the copyright holder. This may be a private person or an organization. Minor modifications should be added without adding a new copyright holder each time. Typically people decide to simply contribute them under the existing copyrights, but finally that's up to the contributor.

Over the years the list of copyright holders may grow to something like

/*
 * Copyright 2002, 2005-2007 by Lisa Simpson
 * Copyright 2001-2006 by Zomtech Ltd.
 * Copyright 1997 by Gondor National University
...

More headers may follow, if the code had been derived from differently licensed code. In the first place you must make sure, that the license is compatible to the BSDL used for Nut/OS. Also note, that some copyright holders explicitly allow to remove the advertisement clause from their BSD license.

More about naming conventions

Much more needs to be specified on this topic.

Conventions for global symbols (function names, variables, macros) help users to remember their precise names.

API functions
Strict CamelCase style is used to separate words in Nut/OS function names, including acronyms that may appear in names.

/* Bad. */
SMTPSend();
Smtp_Send();
/* Good. */
SmtpSend();

Global variables
Lower snake_case style is used to separate words in Nut/OS variable names, including structure members.

/* Bad. */
int usesnmp;
int use_SNMP;
/* Good. */
int use_snmp;

Preprocessor macros
Upper snake_case style is used to separate words in Nut/OS macro names This is true even if the macro is later used like a normal function call. In many cases macro behavior is different from pure functions and it is important for the programmer to know, whether he is calling a macro or a regular function. There are, however, exceptions. Sometimes macros are used to redefine standard functions. For example malloc() might be redefined via a macro for debugging purposes. In this case, we stick to the original function naming, because we want to preserve existing code.

/* Bad. */
#define SmtpSend(x)
#define SMTP_Send(x)
#define MALLOC(x) malloc_debug(x)
/* Good. */
#define SMTP_SEND(x)
#define malloc(x) malloc_debug(x)

Use simple local names
Cobol and Modula are for chatty people, C is for chiefs. While chatty people name a variable 'pointerToTheSourceString', chiefs will simply call it 'src'.

If a chief uses a variable name with 8 or more characters, then this will be most probably a global variable. However, chiefs will rarely use global variables.

Global names
Global variables, functions or macros shall use long names to make them as unique as possible.

Functions starting with 'Nut' are reserved for the Nut/OS kernel. Functions starting with 'Net' are reserved for Nut/Net.

Message and documentation conventions

These apply to messages in the Nut/OS source, as well as to Nut/OS documentation.

Follow the Doxygen inline documentation style
Otherwise your changes will not appear in the Nut/OS API reference.

Use well-formed language for messages, not abbreviations or psuedo-English.

Always starts a sentence with a capital letter.

Always end a sentence with a period or other appropriate punctuation.

Never use "..." unless absolutely necessary.

Avoid "!" wherever possible
It's rarely necessary to emphasise a point this much (the same applies to documentation).

Use only one space after a period and before a new sentence.
Don't add a lone space after a period if it's not going to be followed by another sentence.

Don't use unnecessary parentheses in messages and documentation.
Instead, rearrange the sentence.

Miscellaneous

Don't use the IOREG = value, value = IOREG notation
Use outb(), outw(), outr(), inb(), inw() or inr(). This allows trapping and emulation.

Be prepared to use stdint types
Nut/OS currently uses BSD-style type definitions defined in types.h. They are most often used in TCP/IP implementations, but rarely used for embedded system applications. For example, BSD uses u_char as a shortcut to unsigned char and u_long as a shortcut to unsigned long, while embedded programmers often prefer types like u08 and u32 resp.

Both variants have week points, and had been introduced before stdint.h became widely available. However, it will require some effort to replace all the BSD style types.



Happy coding!

Castrop-Rauxel, February 26th 2008