C Language Standards

Legato follows the C Language standards outlined in this topic to help simplify coding and help in code reuse.


There's also detailed info on Naming Standards and Abbreviations

Commenting

If it's not completely obvious what's going on, it must be documented clearly using comments.

Comments should also be used to improve readability, wherever appropriate.

However, there's no point in commenting the obvious, like this:

// Set the flag
isReady = TRUE;

Comments should focus mainly on describing why something is happening, rather than what or how. Code should be written so that what it's doing, and how it's doing it, is obvious (because of the well named identifiers and cleanly structured code). If not, your should probably rewrite the code. If that's not practical, then you should comment on what and how.

The idea of why the code is doing what it's doing can't be conveyed well in the code itself. This is the most valuable information to have in comments to advise of pitfalls and help quickly identify whether design change options are viable. Often, the ''why'' was learned the hard way, so documenting it can save others from having to learn it the hard way, too. Also, sometimes things are done arbitrarily, which is good to know too so people don't have to fear that making changes to it might break something in some subtle way.

C++ style comments are permitted in C code. All modern compilers support them.

Each module should contain a comment block at the top of the file that describes the module's purpose. The header comment block should be formatted like this:

/**
* @file le_basics.h
* There are certain cardinal types and commonly-used constants that form the most basic
* foundation upon which everything else is built. These include things such as
* error codes, portable integer types, and helpful macros to make ugly things nicer to look
* at and easier to use.
* <HR>
* Copyright (C) Sierra Wireless Inc. Use of this work is subject to license.
*/

Each function should be preceded by a comment block that describes the purpose of the function, its possible return values, and any side effects it may have. The function blocks should be formatted like this:

//--------------------------------------------------------------------------------------------------
/**
* Creates a sub-pool.
* See @ref sub_pools for more information.
* @return
* A reference to the sub-pool.
*/
//--------------------------------------------------------------------------------------------------
le_mem_PoolRef_t le_mem_CreateSubPool
(
le_mem_PoolRef_t pool, ///< [IN] The super-pool.
const char* name, ///< [IN] Name of the sub-pool (will be copied into the
/// sub-pool).
size_t numObjects ///< [IN] Number of objects to take from the super-pool.
);

Function parameters should be documented with comments after the parameter like above. Placing the comments after the parameters (instead of in the function comment block) keeps the comments and parameters together making it easier to read. It also reminds the developer to update the comments when the parameters are changed.

A struct should be documented in a similar manner where each field has comments proceeding it like this:

//--------------------------------------------------------------------------------------------------
/**
* List of memory pool statistics.
*/
//--------------------------------------------------------------------------------------------------
typedef struct
{
uint64_t numAllocs; ///< Number of times an object has been allocated from this pool.
size_t numFree; ///< Number of free objects currently available in this pool.
size_t numOverflows; ///< Number of times le_mem_ForceAlloc() had to expand the pool.
}

Enumerated types should be documented similarly.

//--------------------------------------------------------------------------------------------------
/**
* Example return codes.
* @note These return codes are only valid within the ExampleModule. Use realMeaning(eg_code_t) to
* translate. The following is strictly fictional and created only to illustrate style.
*/
//--------------------------------------------------------------------------------------------------
typedef enum
{
EG_PERFECT = 0, ///< Things went well. Nothing to worry about.
EG_TROUBLE = -1, ///< We might want to think about packing our bags.
EG_PANIC = -2, ///< Definitely not good.
EG_WARP_CORE_BREACH = -99 ///< Dilithium crystals defocused the anti-matter beam. Should never happen.
}
eg_code_t;

If the meaning of the constants in an enumerated type are obvious based on their names, comments could be omitted like this:

//--------------------------------------------------------------------------------------------------
/**
* Boolean type.
* 0 is false and 1 is true, in keeping with the C programming language's
* Boolean expression evaluation rules.
*/
//--------------------------------------------------------------------------------------------------
typedef enum
{
FALSE = 0,
TRUE = 1
}
bool;

Const Pointers

Pointer type function parameters must be declared const if the object pointed to will not be modified by the function being passed.

Pointer type return values must be declared const if the object being returned must not be modified by the caller.

Cyclomatic Complexity

Cylcomatic Complexity is a measurement of the complexity of code within a function. It's measured by counting branches within a function.

Each function gets a starting value of 1, and 1 is added for every "if" (or "?"), every loop (for, do, or while), and every "case".

The following code has a cyclomatic complexity of 3:

int main(int argc, char** argv)
{
int i;
int result = 0;
 
if (argc <= 0)
{
printf("No arguments provided.\n");
result = 1;
}
else
{
printf("argc = %d\n", argc);
 
for (i = 0; i < argc; i++)
{
printf("argv[%d] = '%s'\n", argv[i]);
}
}
return result;
}

Higher levels of cyclomatic complexity are correlated with higher defect density.

All functions should have a cyclomatic complexity of 10 or less.

All functions must have a cyclomatic complexity of less than 15.

Existing Code

When modifying previously written code, the pre-existing style should be used over the Legato standards. But you should apply the Legato standards to the parts you modify unless you're making a very small change.

Extern

When global variables aren't used and all inter-module interfaces are defined in header files, the extern keyword isn't needed; don't use it. Using extern indicates poor coding practices.

Fan Out

Fan-out measures the number of different functions called by a function including the number of data structures it updates. High levels of fan-out indicate insufficient abstraction and results in higher defect density.

The following function has a fan-out of 5:

static int Log(int value)
{
if (IsAboveThreshold(value))
{
LogEntry_t* entryPtr = AllocEntry();
if (entryPtr == NULL)
{
ReportError("Out of memory!");
}
else
{
entryPtr->value = value;
entryPtr->timestamp = GetTimestamp();
 
// Add the entry to the log entry list.
LogEntryList[NextEntryIndex++] = entryPtr;
if (NextExtryIndex >= LOG_SIZE)
{
NextEntryIndex = 0; // wrap around
}
if (NextEntryIndex == LastEntryIndex)
{
ReportError("Log overflow! Log entry discarded.");
LastEntryIndex = (LastEntryIndex + 1) % LOG_SIZE;
}
}
}
}

The functions IsAboveThreshold(), AllocEntry(), ReportError(), and GetTimestamp() are called by the Log() function. The "Log" data structure (consisting of the variables LogEntryList, NextEntryIndex, and LastEntryIndex) is updated by the Log() function. The second and subsequent calls to the same function are not counted. Even though ReportError() is called twice by Log(), it only contributes 1 to the fan-out.

Ideally, fan-out should be kept to 7 or less, and must be kept to 10 or less.

Function Parameters

The number of parameters passed to a function should be kept as low as possible. Functions with less parameters tend to be easier to understand and easier to use. C functions should have 3 parameters or less.

Global Variables

Global variables are variables that are exported to other modules (i.e., have a scope that spans multiple files).

Global variables are dangerous because they don't protect from multithreaded race conditions, and they reduce maintainability because of the increased coupling.

Globals must not be used. Use accessor functions instead.

NOTE: file-scope static variables are fine.

Gotos

goto statements should not be used. If it's absolutely essential, use them sparingly only as a jump-to-exception-handling mechanism:

{
Rec_t* recPtr = CreateRec();
 
...
 
if (x > LIMIT)
{
goto fault;
}
 
...
 
SaveRec(recPtr);
return SUCCESS;
 
fault:
 
ReleaseRec(recPtr);
return FAILED;
}

Heap

Depending on the algorithm used, dynamic memory allocation using a memory heap (e.g., malloc, free, and variants like calloc, realloc, and strdup) can lead to heap fragmentation resulting in unexpected runtime failures. And heap allocation and deallocation can be very slow in some cases.

Use memory pools instead. Memory pools eliminate internal fragmentation, run in O(1) time (for both allocation and deallocation), can be named for diagnostics purposes, allow finer-grained memory allocation statistics collection, and can provide OO constructor and destructor functionality.

Interface Documentation

Inter-component interfaces should be documented using Doxygen. This ensures documentation is inside the include (.h) files.

Line Length

Lines of code should not be longer than 100 columns.

Multiple Inclusion Guards

To prevent declaration errors due to multiple inclusion of the same header file, every header file must include a "multiple inclusion guard" like this:

#ifndef UNIQUE_INCLUDE_GUARD
#define UNIQUE_INCLUDE_GUARD
 
...
 
#endif

UNIQUE_INCLUDE_GUARD must be replaced with a macro name unique to this header file. To ensure uniqueness, the macro name should:

  • contain the name of the file (converted to all upper case, with underscores separating words)
  • be prefixed with a name or abbreviation that's unique to the module that the header file is a part of
  • have "_INCLUDE_GUARD" as a suffix.

The Converter module's inter-module interface file converter.h' in the Sierra Wireless (SWI) Transmogrifier component could contain a multiple inclusion guard macro named like this:

SWI_TMOG_CONVERTER_INCLUDE_GUARD

Code in a header file must be inside the file's multiple inclusion guard (except for comments) like this:

// Only comments allowed here.
 
#ifndef SWI_TMOG_CONVERTER_INCLUDE_GUARD
#define SWI_TMOG_CONVERTER_INCLUDE_GUARD
 
// Any code can go here.
 
#endif // SWI_TMOG_CONVERTER_INCLUDE_GUARD
 
// Only comments allowed here, but preferably nothing but the end of file should be here.

To allow include guard macros to be renamed (in case the file is renamed, the code changes hands, a naming conflict is found, etc.) and to avoid maintenance confusion, these macros shouldn't be used for anything else other than the multiple inclusion guard.

Note
If you're modifying existing code that uses multiple inclusion guard, follow the pre-existing code style instead of the Legato standard.

Multithreading

Sometimes multithreading can be a powerful tool to allow functionally-related code be grouped into a single control flow where it would otherwise be fragmented into small chunks that can run without blocking. But multithreading can easily create some of the most nasty bugs.

What can we do in coding standards to help prevent bugs arising from multithreading? Perhaps defining how synchronization is performed? Can we have a standard macro definition for mutual exclusion for example? Maybe that's outside of what coding standards normally define, but it could be useful.

Recursion

Recursion can be dangerous because it can result in stack overruns. Don't use recursion unless you can clearly highlight the recursion and prove that the recursion will be bounded well within the limits of even the smallest stack space that could reasonably be allocated to your thread.

Separating Interface from Implementation

Header files should contain only interface details. Implementation details should appear only in .c files.

Separating interface from implementation reduces coupling, which increases quality.

Tabs

Tab characters are not handled in a consistent way in editors and browsers. Some put tab stops at the equivalent of 8 spaces, others use 4 or other variations. As a result, source code containing tab characters are not rendered properly in all cases.

Avoid tab characters avoided in C source code. Use spaces to indent lines instead.

The standard indentation distance is 4 spaces per indentation level.

Configure your editor(s) now! (...and, while you're at it, tell your editor not to automatically go and change pre-existing code.)