Contents

 

1      Synopsis.. 2

1.1        Disclaimer. 2

2      Standards.. 3

2.1        No dynamic memory allocation.. 3

2.2        Minimize the public interface of every class. 3

2.3        Avoid type casting. 3

2.4        No compiler warnings. 3

2.5        No explicit constants. 4

2.6        The NULL macro.. 4

2.7        #define macros. 4

2.8        #define / #if / #else / #endif. 4

2.9        Constructors / Destructors. 4

2.10       Pass / Return objects by reference. 5

2.11       Protect header file with #ifndef read-once latch. 5

2.12       Minimize the #include statements in your header files. 5

2.13       Avoid the global namespace unless absolutely necessary. 5

2.14       Virtual Functions. 5

2.15       Member Initializers. 5

2.16       Error handling and Robustness. 6

2.17       Test early.. 6

3      Style Guide. 7

3.1        Comments. 7

3.1.1        Header Files. 7

3.1.2        CPP Files. 7

3.2        Naming. 7

3.2.1        Constants. 7

3.2.2        Classes, Methods, Variables. 8

3.2.3        Files and Directories. 8

3.3        Tabs. 9

3.4        Flow Control Statements. 10

 

 

 

1    Synopsis

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 that 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 is mainly a matter of taste.

 

The coding standards presented here are specifically tailored to working in an embedded environment – where the robustness and reliability of the software is critical.  It is unacceptable for embedded software to periodically reboot because of exhausted/squandered 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.

1.1    Disclaimer

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 an 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.

 

2    Standards

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 and be approved by the software manager or their delegate.  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 and fully justify why the standards do or do not apply.

2.1    No dynamic memory allocation

To prevent memory leaks and fragmentation, no dynamic memory allocation/de-allocation is allowed.  The application may allocate memory (from the heap) at start-up or module initialization, but no de-allocating and re-allocating should be done. If this method is used, all memory should be allocated at initialization time to catch out of memory errors that may occur immediately during the development process. It goes without saying that return codes should be checked and proper recourse taken for failed allocations. This practice guarantees the system will not fail over time due to lack of memory. 

 

For objects that need to be dynamically created/deleted, the application programmer is required to pre-allocate a memory pool (on a per object type basis) that will be used to construct an object at run-time. It is recommended that this be accompanied by overloading of the “new” and “delete” operators to maintain familiar syntax (this can also facilitate early stage development by using the real “new” and “delete” operators). Note that depending on implementation, this may require use of the “placement new” operator to create an object as well as various sneaky tricks to call the object’s constructor as well as the object’s destructor, then returning the memory back to its associated memory pool.  Since the availability of the placement new and placement delete operator may vary from compiler to compiler, the new and delete operators should be overridden for the specific class and handled accordingly.

 

Note: It goes without saying that standard lib realloc() and free() shall not ever be used.

2.2    Minimize the public interface of every class.

Avoid making data members public; preferably use inline get/set functions which the compiler can optimize out.  Keep member functions private or protected unless they are truly intended to be part of the public interface.

2.3    Avoid type casting.

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!

2.4    No compiler warnings.

Code that compiles with warnings is not acceptable. Failure to eliminate insignificant warnings can result in overlooking warnings that are significant and lead to problems that would have otherwise been caught by the warning.  While developing code eliminate the warnings as soon as possible, as the warnings could prove to be the source of logic and run-time errors. Disabling a warning should only be done as a last resort with justification comments in the source code.  NOTE: If supported, you should always compile with the compiler switch that treats warnings as errors to enforce this.

2.5    No explicit constants

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.

2.6    The NULL macro

Be careful using 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, while some implementations define it as 0L. In most C++ compilers, just plain 0 (zero) is the generic.

2.7    #define macros

The Preprocessor knows nothing about C++.  Don't use preprocessor macros to do what C++ can do.  Avoid using #define to create an “inline function”, use the “inline” keyword for member functions instead where practical.  Avoid using #define to set the value of a constant, use enumerations instead where practical.  Remember that a #define lives in global namespace. Take advantage of the name space restrictions of enums defined within a class.

2.8    #define / #if / #else / #endif

Make careful use of configuration options using #define/#if/#else/#endif so that the code does not become unreadable. This is one of the main reason MFC is so hard to use.

2.9    Constructors / Destructors

·        Constructors and Destructors should not be declared if not needed; this will save code space in most compilers (reason: the default constructors/destructors supplied by the compiler are usually implemented as inline calls rather than by creating extra code).

·        Never perform critical operations or operations that may fail in a constructor or destructor. The time and sequence of execution are not guaranteed for static instances of classes. As a result, order-dependent initialization cannot be guaranteed and error trapping is next to impossible without exceptions (which EC++ does not have). Very common examples of this are accessing file-system or OS functionality before their respective initialization or after their un-initialization has been done. Instead, use the constructor only to initialize class member variables that would be initialized data if using straight C. Then use an Initialize() function so you can control the order of initialization allowing for dependencies and of course, always test the return codes for validity and take proper actions on errors.

·        If a class has a virtual function, declare the destructor virtual too (only if a destructor is needed). Otherwise, polymorphism will not get the proper destructor called. (Note that a class with a virtual destructor still calls its base class destructors).

2.10  Pass / Return objects by reference

ANSI C and C++ all pass structure parameters by copying the structure onto the stack. Therefore, use reference parameters (or pointers if required, as addressed next) to save stack usage (of course this assumes that you are not going to just make a copy to a local auto variable anyway). 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 the 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.

2.11  Protect header file with #ifndef read-once latch.

Use the following template for the read-once latch:

 

#ifndef _FILENAME_H_

#define _FILENAME_H_

#endif

 

Avoid using the #pragma once directive to accomplish this as not all compilers support the pragma and our effort is to produce reusable source code.

2.12  Minimize the #include statements in your header files.

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.

2.13  Avoid the global namespace unless absolutely necessary.

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 and 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 (not supported in EC++).   

2.14  Virtual Functions

Virtual functions should only be used when polymorphism is needed. The “safe” tendency is if in doubt to make the function virtual, but this can consume large amounts of ROM/RAM space if not necessary. Most embedded compilers have the option of putting the V-Table in ROM thus making just one instance per class. If the compiler has this option, it should be used since the alternative usually results in one V-Table per instance in RAM.

 

2.15  Member Initializers

Avoid using member initializers except for the required usage for const and reference objects. Reason being that they are often confusing to read and often error prone. 

For example this:

class CTest{

public:

    CTest(): m_var1(2), m_var2(m_var1+2){};

    ~CTest(){};

 

    int m_var1;

    int m_var2;

};

 

Yeilds much different results than this:

class CTest{

public:

    CTest(): m_var1(2), m_var2(m_var1+2){};

    ~CTest(){};

 

    int m_var2;

    int m_var1;

};

If you don’t see (and know) why at first glance, then you just found the reason, and this one is much more obvious than a real application can be.

2.16  Error handling and Robustness

ASSERT is not a valid error handler unless also followed by proper handling code to be executed when debug is turned off (and of course we never distribute debug versions…. right). Return values should be checked and properly handled. Failure to do something because of an error is MUCH better than total system shutdown. Actual example: Once had a developer put ASSERT(10 == strlen(phonenumber))  This was in a windows-based 911 system, which means the system stopped operating if someone entered an incorrect length phone number, requiring the program to be restarted and any pending calls to be lost. Hopefully it’s understandable why if (10 != strlen(phonenumber) { //code to handle the error and request re-entering the number } is a more acceptable solution. Basic premise being, if at all possible, the program should continue to operate.

2.17  Test early

Create the individual classes and components early (especially hardware dependent ones), test them thoroughly leaving the test code in the project if space permits. And don’t just test to see that it works, verify that it works for the right reason by tracing through the code to ensure it is performing as expected and handling “unexpected” input appropriately. With that done, you then just tie the pieces together to make the application. The idea being that it’s much better in the later stages of the project to know that you have a reliable toolbox with which to build your application. For example: You shouldn’t have to debug your CTimer class when all you really want to do is determine why something isn’t happening when it should. You should only have to debug your use of the CTimer class that has been proven to work with appropriate unit tests. This reduces the scope and thus complexity of bug fixing at the critical time of customer visibility at the end of the project.

 

 

3    Style Guide

The intent of the style guide is to establish a “style baseline” that all developers are required to follow.  This baseline provides a consistency across the source code files that aids in reading/maintaining 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.

 

Note: DO NOT convert source files to match your style as this causes unnecessary differences when merging and as a result may introduce errors in the merge process.

3.1    Comments

3.1.1 Header Files

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 understand and use the interface.

3.1.2 CPP Files

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 and variable and function names are chosen appropriately, comments in the CPP file add little value other than to explain intricate or confusing code.

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!

3.2    Naming

3.2.1 Constants

Rule:     All #define and enum constants should be uppercase with under bars for word separation. For example: MAX_REFERENCE_COUNT


3.2.2 Classes, Methods, Variables

Rule:     All class names start with an upper case ‘C’. For example: class CTimer;

Rule:     One class per source/header file pair.

Rec:     Source/Header file pairs should be named the same as the contained class minus the preceding ‘C’. For example: timer.h and timer.cpp

Rec:     Function parameters must be named (i.e. foobar(int, int, int) is NOT acceptable) and the names must match in the .h and .c/.cpp files.

Rule:     All public class interface methods and properties should appear first in the class declaration for improved readability.

Rule:   All data member variables are prefixed with an ‘m_’.  For example:

            int m_MyCount;

Rec:     Use mixed case to separate words in names, not an “_”.  For example:

            int GetReferenceCount();

Rec:     Do not abbreviate names/words; long names are preferred over cryptic abbreviations. For example, does NoMenuItems represent the number of menu items or that there are no menu items. Or even worse, how would you expect someone to know what NoMuIs meant?

Rule:     Use nouns and verbs appropriately in naming things. Properties should usually be nouns, methods should usually be verbs. As for classes (which should always be nouns) use appropriate nouns. Data centric objects should generally have names like CTranslation while tool box classes (actors) should have names like CTranslator.  This helps to make the code self-documenting.

Rec:     Namespace restriction of classes is your friend; it allows you to use otherwise unusable “generic” function and variable names that make more sense than “coded” names. For example a File class can use Open() instead of having to use FileOpen(). This improves the understandability of the code.

Rec:     The ability to overload functions in a class is your friend but can be nullified by excessive use of Hungarian notation. Though Hungarian notation is by no means prohibited, you should make use of type abstraction by NOT using Hungarian notation for member function names. For example, just make overloads of GetCount() instead of iGetCount(), lGetCount, fGetCount(), etc.

3.2.3 Files and Directories

Rule:     Almost all OS/Platform support ‘long’ filenames – use them.  Do not ‘encrypt’ your file and directory names into an arbitrary 8.3 format.

Rule:     Do not use case only differences in file and directory names.  Not all file systems (and revision control systems) are case sensitive with respect to names.

Rule:     Do not use spaces in file or subdirectory names, some tools do not handle them nicely.

Rec:     Avoid using dashes (-), underbars (_) and dots (.) in directory names.

Rec:     Most C++ documentation recommends naming header files without the .h extension, but continuing to use the .h extension allows for shell activation of the appropriate editor.

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.


3.3    Tabs

Rule:     Tab stops will be set to 4!  If the editor supports it, spaces should be inserted instead of tab characters (reason: most text tools and printers use 8 space boundaries for tabs; using spaces guarantees the code will maintain it’s format when viewed or printed).

Rule:     When modifying someone else’s code, follow the original author’s tab style; whether or not it agrees with your own personal preference.

 

Note: Unless you are taking total ownership of the code, DO NOT convert source files to match your style as this causes unnecessary differences when merging and as a result may introduce errors in the merge process.

 


3.4    Flow Control Statements

Rec:     The flow control primitives if, else, while, for and do should be followed by a block, even if it is an empty block or contains only one statement. The reason of course is that later modifications often lead to forgotten brackets.  For example:

while( /* do something */ );  // Bad

 

while( /* do something */ )   // Good

{

}

 

if( isOpened() )        // Bad

   foobar();

 

if( isOpened() )        // Good

{

   foobar();

}