Embedded C Coding Standards

Coding standards are not the best, only the most suitable. Having them but not implementing them is worse than not having them at all.

1. Coding Principles

1. Principle of Readability

Clarity First: Clarity is a necessary characteristic for maintainable programs. The cost of changing code during the maintenance phase far exceeds that of the development phase. Programs should be human-centered, with computers second. Generally speaking, code readability takes precedence over performance; only when performance is identified as a bottleneck should optimization be actively pursued.

Simplicity is Beauty: Simplicity means easy to understand and easy to implement. The longer the code, the harder it is to read, and the easier it is to introduce errors during modification. Advocating for clear and concise code enhances code reliability. Obsolete code should be promptly removed, and duplicate code should be refined into functions whenever possible.

Consistent Style: Everyone should share the same style, facilitating later maintenance and code handover.

2. Design Principles

Open-Closed Principle: Open for extension, closed for modification.

Single Responsibility Principle: Each sub-function or similar code block should have only one responsibility, meaning there should be only one reason for it to change.

Interface Segregation Principle: Interfaces should be as fine-grained as possible, and methods within the interface should be kept to a minimum.

Least Knowledge Principle: A sub-module should have the least knowledge about other modules.

Dependency Inversion Principle: High-level modules, low-level modules, and details (implementation) should all depend on abstractions (i.e., interfaces).

2. Coding Standards

1. File Header Declaration

◎ New .c files must include comments indicating the company name, file functionality description, creation date, author, and subsequent modification notes. An example is shown below:

/*********************************************************************
* Copyright (c)  Hehe,Ltd. All rights reserved.
* Description: Detailed description of the main functionality of this program file,
*             and its interface dependencies with other modules or functions.
*
* History: Modification history list, including modification date, modifier, and a brief description of the modification.
* Date               Author                Modification:
* 2023-11-25        embedded-system        create v1.0
*********************************************************************/

Configurable Source Insight can automatically generate templates.

2. Files

◎ All .h header files must implement a mechanism to prevent content from being included more than once.

#ifndef __XXX__
#define __XXX__
#endif  /*__XXX__*/

◎ The external interface of header files should include external declarations, such as function declarations, macro definitions, type definitions, etc.

◎ Internal function declarations should not be placed in header files.

◎ Internal macros, enumerations, and structure definitions should not be included in header files.

◎ Variable definitions are prohibited in header files and should be placed in .c files.

◎ Global variables used within a module should not be directly exposed to the outside through declarations in header files.

◎ Header files should only contain declarations of interfaces, not implementations.

◎ Header files should have a single responsibility; overly complex header files with complex dependencies are a major cause of long compilation times.

◎ Each .c file should have a corresponding .h file for declaring interfaces that need to be publicly exposed.

◎ Cyclic dependencies in header files are prohibited, and unnecessary header files should not be included.

◎ The content of each .c source file should follow the order: file comments – include header files – macro definitions – data structure definitions – variable definitions – reference external variables – reference external functions – local functions – global functions.

3. Functions

◎ A function should only perform one task.

◎ Duplicate code should be refined into functions whenever possible (Note: Refactoring duplicate code into functions can reduce maintenance costs. Duplicate code is one of the most typical characteristics of bad code. Under the guiding principle of “if the code works, don’t change it,” new requirements lead to code duplication and modification, resulting in many similar or duplicate codes piling up in the product over time.)

◎ Avoid deeply nested code blocks in recursive functions.

◎ Error return codes from functions should be comprehensively handled (Note: A function (standard library function/third-party library function/user-defined function) can provide some indication of an error occurring, which can be checked immediately by the calling program upon function return using error flags, special return data, or other means.)

◎ Obsolete functions should be promptly removed (Note: Obsolete code in programs not only occupies extra space but often affects the functionality and performance of the program, potentially causing unnecessary trouble for testing and maintenance.)

◎ Unchanged parameters passed to functions should be restricted using const.

◎ The number of parameters for functions should not exceed 5, and input parameter validity should be checked (Note: Having too many parameters makes the function susceptible to changes from external (other parts of the code), affecting maintenance work. Too many parameters also increase the workload for testing. The number of parameters for functions should not exceed 5; if it does, it is recommended to split them into different functions. Input to functions can be either parameter input or global variable/data file input, i.e., non-parameter input. Validity checks should be performed before using input parameters.)

◎ All functions declared and defined within the scope of the source file should be marked static unless they are externally visible; for unit testing, it is recommended to encapsulate such functions before use.

◎ Parameters that are passed in should not use magic numbers, and enumerated values must be used with accompanying comments if there are more than three meanings.

◎ Functions should check the legality of parameters internally (Note: The input to functions can be either parameter input or global variable/data file input, i.e., non-parameter input. Validity checks should be performed before using input parameters.)

◎ Avoid using variable-length functions, except for printing functions (Note: Variable-length functions are complex to handle and prone to errors, and using too many variable-length functions significantly increases the difficulty of maintaining functions.)

◎ Each function must return an error code, and the calling program must check the error code upon function return.

◎ Identifier naming should be clear and meaningful, using complete words and avoiding numbers or special symbols in names.

◎ Function names should reflect the specific functionality of the function and should be composed of functional words, and Chinese pinyin is strictly prohibited.

◎ Function naming should reflect the action the function performs, generally using a verb or a verb+noun structure.

4. Variables

◎ Avoid or minimize the use of global variables (Note: Static global variables can be used internally within a single file and can be understood as private member variables of a class. Global variables should be private data for the module and should not be used in external interfaces. Using static types can effectively prevent abnormal access from external files. Directly using private data from other modules can lead to a coupling state that is difficult to untangle, which is not allowed.)

◎ Avoid naming local variables the same as global variables (Note: Although local and global variables have different scopes and do not cause syntax errors, it can lead to confusion.)

◎ The use of uninitialized variables is strictly prohibited.

◎ Clearly define the initialization order of global variables to avoid cross-module initialization dependencies (Note: During system startup, when using global variables, consider when the global variable is initialized, the timing relationship between the two, and analyze it clearly; otherwise, the consequences can be low-level and disastrous.)

◎ When data must be exposed, encapsulate interface functions for reading and writing, and ensure mutual exclusion in accessing global data (Note: Avoid directly exposing internal data to external models is the simplest and most effective way to prevent module coupling.)

◎ A variable should have only one function and should not be used for multiple purposes (Note: A variable should only represent a specific function, and should not serve multiple purposes, meaning that when the same variable takes different values, its meaning should also change.)

◎ Data structures should have single functions and should not design overly comprehensive data structures (Note: A related group of information is the basis for defining a structure. The definition of the structure should clearly describe an object, rather than a collection of loosely related data. When designing structures, strive to make the structure represent an abstraction of a real-world affair rather than representing multiple things simultaneously. The elements in the structure should represent different aspects of the same affair, and not elements describing unrelated or weakly related affairs.)

◎ Minimize unnecessary type conversions and forced conversions (Note: When performing forced type conversions, the meaning of the data, the values after conversion, etc., may change, and if these details are not considered carefully, they could lead to hidden dangers.)

Example: The following assignment does not raise warnings in most compilers, but the meaning of the value changes.

char ch;
unsigned short int exam;
ch = -1;
exam = ch; // The compiler does not raise warnings, at this point exam is 0xFFFF.
◎ Confirm that unused variables should be deleted.

◎ The use of increment (++) and decrement (–) on variables is prohibited in macro definitions and should not be combined with other statements; they should be split and executed separately.

Example: if(++i>10) is incorrect; it should be changed to i++; if(i>10)

5. Macros and Constants

◎ Macro definitions and constants should use uppercase letters or underscores.

◎ When using macro-defined expressions, complete parentheses should be used as follows:

#define HEHE_AREA(a, b) ((a) * (b))

◎ Avoid using statements that change program flow, such as return, goto, continue, break, etc., in macro definitions.

◎ Constants are recommended to be defined using const instead of macros, as shown below:

#define ASPECT_RATIO 1.653 replaced with const double ASPECT_RATIO = 1.653;

◎ Unless necessary, prefer to use functions instead of macros.

◎ Place multiple statements in macro definitions within curly braces.

◎ When using macros, parameters should not change.

◎ Minimize the use of magic numbers, or if necessary, add comments explaining them, or modify plans, such as prohibiting the use of constants for memory length operations; under special circumstances, use sizeof to handle automatically.

6. Naming

Use a Unix-like style for naming, using lowercase letters for words, separating each word with underscores, and maintaining the original style for referenced third-party code; naming should use common English words or abbreviations as much as possible.

Files: File names can be named according to platform-specific rules, generally using lowercase characters, with fields separated by underscores; .c and .h files with the same functionality should have the same name.

Enums: Macro definitions and enumeration values should not use lowercase letters, should not start with underscores, and should separate fields with underscores. If multiple states need to be marked in logic, states should not be represented by numbers.

Structures: If parameters used for the same function can be defined using a structure, it is easier to obtain and set related parameters. Pure business logic code that is platform-independent must use lowercase characters and underscores.

Functions: Function names should reflect the specific functionality of the function, composed of functional words using lowercase letters and underscores, with global functions prefixed by xx_. Global functions should be declared in .h files with complete comments; local functions should be restricted using static.

Variables: The following points should be noted:

◎ Prohibit the use of all uppercase letters for naming variables; global variables should have at least 5 letters, and frequently used global variables should be kept short.

◎ The naming of global variables should express their function and begin with the lowercase letter g_, followed by the functional English word, such as address: g_addr.

◎ Variable name concatenation should use all lowercase letters and underscores; local variable names within functions may use single letters.

◎ Multiple similar variables should be encapsulated into structures.

Recommended Naming:

add/delete  begin/end  create/destroy  insert/delete
first/last  increment/decrement  get/set/release  up/down
lock/unlock  open/close  min/max  old/new
start/stop  next/previous  source/target  send/receive
7. Comments

◎ Comments should be placed directly above or to the right of the corresponding code, not below.

◎ The content of comments should be clear and unambiguous.

◎ Update comments synchronously when modifying code to ensure consistency between comments and code.

◎ Comments at the function declaration should describe the function’s functionality, performance, and usage, providing a reference template as follows:

/**
 * @brief    Function functionality
 * @params 
 * @return
 * @par history
 *   xx create  @2023-xx-xx
 */

◎ Global variables should have detailed comments.

◎ Internal function comments: Having many comments inside a function is not always better; clear variable naming and logic is preferable, with self-documenting code being ideal. Only add comments for special cases or points needing attention, and place comments above the code lines.

◎ When modifying code based on an SDK, do not delete source code; modifications must include comments, using the keyword “XX_CODE” to indicate modification reasons, facilitating later patching, as shown below:

/**** XX_CODE begin ****/
/*Modification reasons, author, date*/
<code block="">
/**** XX_CODE end ****/

For non-C source files, add the corresponding comment symbol to each line based on this comment format.

◎ Modifications related to peripheral drivers, communication protocols, system-level code with special implicit restrictions must include detailed modification reasons to facilitate future version tracing.

◎ For complex and relatively independent functionalities, use separate markdown documents to explain development plans, implementation technologies, application scenarios, and usage restrictions, submitted alongside the code.

8. Formatting and Style

◎ Program blocks should be written using an indentation style, with each level of indentation consisting of 4 spaces.

◎ Add blank lines between relatively independent program blocks and after variable declarations.

◎ Multiple short statements should not be written on the same line; long statements should be broken into multiple lines.

◎ Statements like if, for, do, while, case, switch, default, etc., should occupy their own lines, and { should also start on a new line.

◎ Assignment statements should not be written within if statements or used as function parameters.

◎ Each item in logical expressions should use parentheses.

◎ if and else if/else must be separated by ‘{}’ and ‘{‘ and ‘}’ should each occupy a line. If-else chains exceeding three layers must end with an else clause, even if the operation is empty, and include a comment: //do nothing

if(var==xx)
{
  …
}
else if (var==yy)
{
  …
}
else if (var==zz)
{
  …
}
else
{
  //do nothing
}
◎ The switch statement must have a default branch.

◎ When performing equal operations on two or more keywords, variables, or constants, add spaces before and after the operators; for non-equal operations (like ->), do not add spaces after.

◎ After completing the file, use the Astyle automatic formatting tool to organize it before submitting to the version library.

astyle.exe --style=allman -S -U -t -n -K -p -s4 -j -q -Y -xW -xV

3. Coding Requirements

1. Security

◎ Perform effective range checks on user input values.

◎ Conduct boundary checks on input parameters, especially pointer variables.

◎ The use of uninitialized variables as right values is strictly prohibited; all variables must be initialized.

◎ Each ordinary variable (integer, character) definition should consider its range to prevent overflow.

◎ Each array definition and usage must strictly prevent out-of-bounds occurrences.

◎ Avoid implicit or explicit type conversions to prevent truncation or loss of sign.

◎ Dynamically allocated memory should be released within the same function; in special cases, comments must be added to remind external users to release the memory.

2. Portability

◎ Do not define, redefine, or undefine reserved identifiers, macros, and functions in standard libraries/platforms.

◎ Extract platform-related common functions, encapsulate them, and place them in fixed files for easier future replacements and upgrades.

◎ Source files must use the original platform or SDK consistent file encoding (GB2312/UTF8); Chinese characters must not appear in valid code.

◎ Use function macros to control common functionalities, ensuring the code is centralized.

◎ Use system interfaces, especially when APIs differ across platforms, encapsulate them with function names containing pal and add comments for ease of future porting to other platforms.

◎ When referencing third-party open-source code, the original style may be retained, but customized modifications must include comprehensive comments.

4. Implementation of Standards

Coding standards are the benchmarks for the collaboration of software development teams, but various uncontrollable factors exist in actual development processes, especially the pressure of project timelines and differences in developer skill levels and understanding, leading to the inability to strictly enforce software rules. As software engineering scales up, risks associated with software delivery timelines, code synchronization, refactoring, or handovers also gradually increase. Therefore, having suitable coding rules alone cannot solve the problem; only enforced code formatting can truly implement unified coding standards.

Software quality is one of the key points for project success. In situations where development cycles are limited and human resources are insufficient, using tools to achieve automatic code scanning to analyze potential issues is crucial for reducing software bugs from the source, ensuring timely software delivery. Implementing automatic code formatting and static analysis can effectively mitigate software risks.

Embedded C Coding Standards

END

Source: Embedded Systems

Copyright belongs to the original author. If there is any infringement, please contact for deletion.
Recommended Reading
The operating system of the Japanese nearly ruled the world…
Understand the differences between hex files, bin files, and axf files in one article
No need for foreign authorization! The milestone of domestically designed CPUs is here~
→ Follow for more updates ←

Leave a Comment