C/C++ Coding Style & Standards
Coding practices for embedded development
Table-of-Contents
2.1 No dynamic memory allocation
2.2 Minimize the public interface of every class.
2.3 Use const wherever possible.
2.8 Do not use preprocessor macros
2.9 Conditionally compiled code is not allowed
2.11 Copy constructor and assignment operator
2.12 Pass/Return objects by reference
2.13 Protect header file with #ifndef read-once latch.
2.14 Minimize the #include statements in your header files.
2.15 Avoid the global namespace unless absolutely necessary.
3.2.1 Classes, Methods, Variables
This document defines the C++ coding standards and style for developing embedded software. Code standards and code style are often mixed together, which is unfortunate. Standards should be reserved for coding practices which bear directly on the reliability of code. Violating a precept of a code standard puts your code in jeopardy of error. Style is another matter. It deals with issues like formatting and naming conventions. More than anything style affects the readability of your code. And it is mainly a matter of taste.
The coding standards presented here are specifically tailored to working in an embedded environment – were the robustness and reliability of the software is critical. It is unacceptable for embedded software to periodically reboot because of exhausted/squander resources (such as no memory available).
The establishment of a common style will facilitate understanding and maintaining code developed by more than one programmer as well as making it easier for several people to cooperate in the development of the same program. Using a consistent coding style throughout a particular module, package, or project is important because it allows people other than the author to easily understand and (hopefully) maintain the code. Most programming styles are somewhat arbitrary, and this one is no exception.
This document is a selective collection of coding style and standards excerpts from numerous white papers and publications. Discussion of style and standards is a long honored tradition in software development. This document is attempt by the author to define a minimum set of style and standards that are helpful for embedded development, but at the same time practical to implement. The idea here is to maximize the “bang for the buck”. Coding style and standards require discipline on the part of the developers to be followed. The more “painful” an organization’s style and standards are, the less likely that the developers will faithfully follow the procedures. Process and its sibling documentation are some of the first casualties of schedule pressures. The moral of the story is to keep your style and standards to a manageable set – especially in time of crisis.
The standards documented here are targeted at reducing errors while developing code. This includes both compile time and run time errors. For the standards to be effective, strict enforcement is required. The following standards should not be violated. Every time a rule is broken, it must be clearly documented in the code. This documentation serves two purposes. First it provides crucial information to other developers when maintaining the code. Second, it forces the original programmer to think about/fully justify why the standards do not apply.
To prevent memory leaks and fragmentation – no dynamic memory allocation is allowed. The application may allocate memory (from the heap) at start-up – but not once the system is “up and running.” This practice guarantees the system will not fail over time due to lack of memory.
For objects that must be dynamically created/deleted – the application programmer is require to pre-allocate a memory pool (on a per object type basis) that will be used to construct object at run-time. This requires the developer to use the “placement new” operator to create an object. To delete an object, the developer must explicitly call the object’s destructor and then return the memory back its associated memory pool.
Note: It goes without saying that malloc(), realloc()) and free() shall not ever be used.
Do
not make data members public. Keep
member functions
private or
protected unless they are truly intended to be part of the public interface.
const
wherever possible. constness is next to Godliness. Every possible modification of data should occur
according to explicit relaxation of default
read-only policy. Think
const.
Member functions should be
const
by default. Only when you have a clear, explicit reason should you omit the
const modifier on member functions.
Take every possible measure to avoid type casting. Errors caused by casts are among the most pernicious, particularly because they are so hard to recognize. Strict type checking is your friend – take full advantage of it!
Code that compiles with warnings will not be accepted for integration. Also while developing code eliminate the warnings as-soon-as-possible, as the warnings tend to be the source of logic and run-time errors. NOTE: You should always compile with the compiler switch that treats warnings as errors.
Do no write explicit constants into code, except to establish identifiers to represent them. The exception to the rule is the constant “0”. Always use the C++ keywords true and false for Boolean values.
Always use 0, never NULL, in C++. The “Annotated C++ Reference Manual”, by Ellis and Stroustrup [Addision-Wesley, 1990] does not guarantee that the defined constant NULL is compatible with all pointers types. Some implementations define NULL as (void*)0, which cannot be assigned to another pointer type without a cast.
The
Preprocessor knows nothing about C++.
Don't use preprocessor macros to do what C++ can do. Do not use
#define to set the value of a constant or create an “inline
function”. Use enumerations or
const variables, and/or inline functions instead of
preprocessor macros.
Do not use the preprocessor #ifdef/#ifndef constructs to support platform/hardware/system dependent code. All platform/hardware/system dependent code must be isolated into individual files that are specific to the dependencies. This means the developer – at the design stage – must identify and plan for handling these dependencies!
If a class has a virtual function, declare the destructor virtual, too.
Always declare a copy constructor and assignment operator for a class that has responsibility for dynamic storage. The default copy operation provided natively by the compiler only provides a ‘bit-wise’ copy. This default operation does not make a copy of what ever is dynamically stored in the class. By explicitly implementing the copy and assignment methods the developer is able to provide a “deep” copy of the class. The copy/assignment operations are responsible for correctly copying the dynamic elements of the class. NOTE: Strict adherence to the “No dynamic memory allocation” rule makes this rule moot.
References provide stricter semantics than raw pointers (i.e. a reference requires that the object it “points to” exists – where a raw pointer does not). If there interface semantics are such that the object must always exist – use references in the interface even if the internal implementation is using pointers. By using references in interfaces whenever possible, the compiler can enforce more of the semantics of the interface.
#ifndef read-once
latch. Use the following template for the read-once latch:
#ifndef _namespace_classname_h_
#define _namespace_classname_h_
…
#endif
For example
#ifndef _jcl_InputStreamApi_h_
#define _jcl_InputStreamApi_h_
…
#endif
Headers files should only include those (header) files that are required for header file to compile. Do not include files that are only used by the associated .cpp file. The #include statements in your header files define the dependency of the file –fewer the dependencies the better.
It should be obvious why you want to avoid polluting the global namespace. If you ever “import” code from a developer, department, third party vendor, etc. there is potential for collision in the global namespace. If a collision occurs – someone’s code must change. Modifying “proven” code is bad thing! The two common options to avoiding polluting the global namespace are: 1) Nest enumerations, constants, helper classes, etc. inside of existing classes. 2) Use the C++ namespace feature.
The intent of the style guide is to establish a “style baseline” that all developers are required to follow. This baseline provides a consistence across the source code files that aids in reading/maintain the code. In addition, following a common programming style will enable the construction of tools that incorporate knowledge of these standards to help in the development and maintenance of the code.
This guide specifies only basic requirements – the individual programmer is allowed to impose his/her own style/preferences on top of these requirements. There are only two absolute rules to observe when it comes to “creating your own” coding style. The first rule of coding style is consistency:
Establish a style and stick to it.
The second rule of style is tolerance:
When you must modify code written by others, conforming to a different style, adopt that style, do not convert the code to your style.
Rule: Header files must be completely documented. This means every class, method, and data member must have comments. Header files describe the interfaces of the system, and as such, should contain all the information a developer needs to use/understand the interface.
Rule: The quantity/quality of the comments in a CPP file is left to the individual developers to decide. If the corresponding header file is completely documented, comments in the CPP file add little value.
Rec: Add comments whenever you feel that the code is complex, non-standard, and/or clever. Remember if you want the luxury of other people maintaining your code – they must be able to understand it!
Rec: Document your code so that an external tool can extract the information stored in your header file comments. For example doc++ and doxygen are freely available tools that are able to generate online (HTML) and offline (Postscript, man pages, etc) documentation from the raw source code. Detailed information on doc++ and doxygen can be found at: http://www.zib.de/Visual/software/doc++/, http://www.doxygen.org. The following are examples of doc++ comment styles.
/** This abstract class …
*/
class FooBar {
public:
/// Enables the Widget to begin sending data
virtual void enableWidget() =0;
};
Rule: All class names start with an upper case letter.
Rule: All function names start with a lower case letter.
Rule: All data members are prefixed with an ‘_’. For example:
int _myCount;
Rec: Use mixed case to separate words in names – not an “_’. For example:
int getReferenceCount(); // OK
int get_reference_count(); // NOT OK
Rec: Do not abbreviate names/words; long names are preferred over cryptic abbreviations.
Rule: Almost all OS/Platform support ‘long’ filenames – use them. Do not ‘encrypt’ you file and directory names into an arbitrary 8.3 format.
Rule: Do not use mixed case in file and directory names. Not all file system (and revision control systems) are case sensitive with respect to names.
Rec: Avoid using dashes (-), underbars (_) and dots (.) in directory names.
Rec: Directories are your friend. Do not hesitate to create directories even if they only contain other directories or a single file. Directories are a great tool for organizing and “naming” dependencies between source code files and packages.
Rule: Tabs stops will be set to 4! The actual use of spaces vs. tabs when indenting is left up to the individual.
Rule: When modifying someone else’s code – follow the original author’s tab style – whether or not it agrees with your own personal preference.
Sidebar: It is amazing at how “religious” programmers can get about the use of tabs vs. spaces. I have met more than one programmer who would rather be fired than change his tab style. The subject of tab style ranks up there with the topics of curly braces and text editors for “religious fervor.”
Rec: The flow control primitives if, else, while, for and do should be followed by a block, even if it is an empty block. For example:
while( /* do something */ ) // Bad
;
while( /* do something */ ) // Good
{
}
Rec: The block following a flow control primitive should always be bounded by brackets even it the block contains only one statement. For example:
if( isOpened() ) // Bad
foobar();
if( isOpened() ) // Good
{
foobar();
}