Embedded C Coding Standards
- 1 Embedded C Coding Standards
- 2 Principles
- 3 Best Practices
- 3.1 Modularize Code
- 3.2 Functions
- 3.3 Validate Pointers
- 3.4 Variables
- 3.5 Data Initialization
- 3.5.1 Globals
- 3.5.2 Structs
- 3.5.3 Arrays/Maps
- 3.6 Safety and MISRA
- 3.7 Documentation
- 3.8 Balanced length for variables
- 3.9 Avoid magic numbers
- 3.9.1 Timeout example
- 3.9.2 Error code example
- 3.9.3 Size example
- 3.10 Avoid vague names
- 3.11 Use Relative Includes
- 3.12 Module Design
- 4 Data Structure Guidelines
- 5 Software Layering
- 5.1 Whitespace & Code Format
- 5.2 Layers
- 5.2.1 System Architecture
- 5.3 Double Underscore
- 5.4 Include Guard
- 5.5 C++ extern
- 5.6 Order of Includes
- 5.6.1 Header File
- 5.6.2 Private Header File
- 5.6.3 Source File
- 5.6.4 Unit Test file
- 6 Naming Convention
- 6.1 General Rules
- 6.2 Variable Names
- 6.2.1 Limit Abbreviations
- 6.2.2 No Redundant Names
- 6.3 Types
- 6.3.1 Use strong types
- 6.4 Preferred Types
- 6.4.1 Use <stdint.h>
- 6.4.2 Use size_t
- 6.5 Functions
- 6.6 Enums
- 6.7 Structs
- 6.8 Unions
- 6.9 Function Pointers
- 6.10 While Loops
- 6.11 Macros
- 6.11.1 Macro guidelines
- 6.11.2 Macros as compile-time switches
- 7 Other Guidelines
- 8 Code Templates
- 8.1 Header File
- 8.2 Private Header File
- 8.3 Source File
- 8.4 Test File
Embedded C Coding Standards
Coding standards help keep the code clean, and consistent. The guidelines below also explain and cite the reasons why we should follow them, and certain ones will have references to ISO26262, which is a safety certification guideline for code.
Goals
Consistency
Promote Best Practices
Provide Structure to the code
Avoid bugs and increase developer efficiency
Optimize for code reviewers and readers rather than code developers
Non-Goals
Nitpick on the developer code
Argue whitespace issues (
clang-formatauto formats brace positions and spaces)
Any code you write, modify or generate should follow the coding standards. If you find any code that doesn't follow these rules, please take the initiative to fix it.
Follow the Boy Scout Rule
Leave the campground cleaner than you found it.
Principles
Principles are guiding rules to be more successful. You need to constantly keep these in mind and have them serve as a guardrail to keep you focused towards writing good, and clean software.
You should not learn of the following principles once and then forget them. A principle is a kind of rule, belief, or idea that guides you.
[Clean C++]
KISS
Keep It Simple and Stupid and less obscure. Keep the logic as simple as possible, and oftentimes as un-optimized as possible. Aim for code readability over creating fancy logic.
Very cool macro but could be deadly:
#define CRITICAL_SECTION() \
for (int i = ISR_disable() | 1; i != 0; i=ISR_enable() & 0)
CRITICAL_SECTION() {
++x;
}Violation of the KISS principle in an effort to "optimize":
uint8_t array[64];
// Do not do this
for (uint32_t i = 0; i < 64/8; i++) {
*(uint64_t*)&array[i] = 0;
}
// Apply the KISS principle
uint8_t array[64] = { 0 };Aim for extreme simplicity and write minimal amount of code. And remember my wise words:
It is not about how many lines of code you write, it is about how few.
-- Lockheed Martin (Preet)
DRY
Copy and paste is a design error.
-- David L. Parnas
Do not Repeat Yourself The extent of this even means not repeating yourself in comments. In general, whenever you believe a change in logic requires you to make the same fix somewhere else, then it is time to refactor the logic to avoid repeating yourself.
Do not repeat yourself in comments either
// The timeout is set to 100ms because ...
const uint32_t timeout_ms = 100;
// Instead, do this:
// The timeout value is chosen because ...
const uint32_t timeout_for_can_message_ms = 100;But apart from this silly example, do not repeat your code in multiple places. Instead, refactor to a common function, and then re-use that. Effects of this includes:
Avoid updating a bug-fix in multiple places
Unit-test just one piece of code
Maintain just one source of truth
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
-- The Pragmatic Programmer
YAGNI
You Ain't Gonna Need It This one is the most occurring theme among developers. We usually develop APIs that we think we will need one day. But the fact of the matter is that we can design the APIs when we really need it.
Here is a good one:
Do not do something today that you can do tomorrow, because tomorrow, you might not need to do it at all.
IBM Websphere Group (Preet)
Prefer to build software when you really need it, otherwise it is a waste. Avoid your impulses:
I might need it one day
This will make it better for the future
But at the same time, provisioning certain things towards the future is okay. For example, the TCP/IP stack provisioned for some reserved bytes and although the standard is largely unchanged, it did provide the roadway towards IPv6. Another example is that if you are developing a communication standard, it is okay to provision for an extra byte for a version that allows you to amend the standard in the future.
Best Practices
Here are some general Sibros guidelines for being a good, responsible engineer.
Write code for someone else to take over
Strong developers never need to worry about
job securityPass it on such that you can go do even more exciting work
Knowledge is not worth anything until transferred
Take your time, and do things right
Write code as if you are writing safety critical code for a pacemaker or an airplane flight controller
This includes unit-test code; write maintainable unit test code
Apply proper code fixes rather than patches
Any workarounds are going to come back to haunt you
Be thorough, and understand your code fixes
Get to the root cause
There is no such thing as magic
Have a firm definition of
it worksCan you bet your life on it?
Will it work 100% of the time?
Write high quality, deterministic code
Design code such that you will never need to hit the reset button for 1 year, 10 years, and beyond
One Sibros team member's previous colleagues once said
Oh we hit this bug every 24 hours, let's reset our controller every 23 hoursThey never worked with that person again, and another team member gets angry if they ever mention his name
Memory allocation should never fail
Design code such that even if someone attacks your controller through a CAN bus, or Ethernet network, your code will always result in predictable execution
Work with integrity and honesty
Treat your co-workers and stakeholders with respect, even when in disagreement
Communicate concisely and ensure criticism and comments are constructive
Don't be afraid to give honest feedback, and continue to iterate and improve on your work
Start with establishing a good base for your header file before writing your code
All parties can work together to come up with a solution before implementing any code; more ideas are always better!
Reviewing the header file first clarifies any issues with ticket requirements or misunderstandings
This also prevents wasted time developing code, tests and debugging said code that may end up being deleted
Modularize Code
Modularize code, and do not create God Objects. When you are dealing with a large problem, split the problem into smaller code modules and make the code only do one thing. This allows for the module to be implemented with simple logic thus improving the overall code readability as well as making it easier to test.
If we are creating an HTTP client, do not put all the code into a single module. Instead, consider splitting this problem into small code blocks, such as:
A socket library to read the data
A buffer module that the data is read onto
An HTTP parser library to parse the incoming data
Reference this article for further inspiration.
Functions
Write Testable Code
Write code with the test code alongside. This is hard to capture in a guideline like this coding standards' document, but design code that is modular, and testable. Refer to the C Unit Test Tutorial for more details.
Keep the functions small
Do not write functions that are larger than 10 lines of meaningful code. The benefits are:
Creates smaller units of code that are easier to test
Increases code clarity
Decreases code complexity (ISO26262, Table 1: 1A)
Do not use more than 5 brace levels in your code
Refactor code into smaller functions
Re-structure the branch statements to reduce the curly brace nesting
This improves the overall readability of the code
No recursive functions
In Embedded Systems, the stack memory is precious, as there is usually not very much.
Each function should only have one return statement
and that should be at the end of the function. This is to prevent unintentional side-effects where a developer adds some code thinking it will be run, but the function exits out before it gets to that piece of code.
MISRA and safety certified code requires this
Use
(void)for functions without inputsA function with no input should be labeled as
func(void)for the parameters. This is because if you omit the void in C, you can pass it infinite number of parameters. C++ safeguards you for this case though.
Prefer at most 10 meaningful lines per function
This forces you to make the code more modular
This helps during unit-testing
Avoid using more than 5
{}brace levels in your code
Use Yoda notation in equality checks but not anywhere else.
Example: Use
if (5 == value)instead ofif (value == 5)Example: Use
if (const_x != value)instead ofif (value != const_x)Example: Do not use
if (5 < value); see Yoda notationExample: Do not use
if (10 >= value); see Yoda notationBecause we don't want to accidentally assign the variable in the condition check.
Even though static analysis should catch this, we do not want to rely on it.
PCLint currently detects and fails on accidental assignment in condition for both C and C++, so Yoda notation is no longer required (but still recommended)
if (constant == my_variable) { } // Good
if (my_variable == constant) { } // Bad
if (constant != my_variable) { } // Good
if (my_variable != constant) { } // BadUse
consttype qualifier on pointer parameters when applicable (i.e. when the referenced value remains unchanged).Do not use this type qualifier on pass-by-value parameters as it is redundant.
const char *app__get_log_line(const *module_s); // Good
bool app__is_id_ready(const *module_s, const size_t type_id); // Bad, const type_id is redundantValidate Pointers
Assuming there is a heavy use of pointers, we want to balance the fundamental checks with those that may actually be useful and create a safer code-base.
C code modules are designed based on structs and the instances of these structs are then given to APIs. This mimics how C++ classes operate as the first parameter to functions is the module pointer itself which C++ refers to as the this pointer.
typedef struct {
void * data;
} my_module_s;
void my_module__api(my_module_s *this_ptr);Validate and check all pointers being passed into public API
We used to avoid this, but had to revert our decision based on safety certified code requirements. It introduced extra brace level and increased cyclomatic complexity, but it was necessary, and hence the next bullet point is striked-out.
Do not check the
thispointer equivalent of C++ withNULL. This creates a lot of fluff, creates potential waste that will only be rarely useful because it is a programming error to passNULLpointers and we cannot be over-protective about this condition.
Private functions of the module can omit additional pointer checks as long as you can ensure through unit-testing that those pointers could never be bad memory pointers
The rest of the pointers should be checked and design code such that error conditions will not end up in a CPU exception.
There may be reasons for not checking the this_ptr, however, the safety aspects of the code outweigh the benefits. Here are some downsides that we did assess though:
Unit-tests double since each method needs a test for the
NULLpointerEach function would have additional brace level (early returns are forbidden due to MISRA/C)
We (used to) consider passing a
NULLpointer as a user error, and do not believe it adds significant value
void my_api(module_s *this_ptr, void *ptr) {
if (NULL != this_ptr) {
// Ensure that bad pointers do not execute an un-deterministic instruction
if (NULL != ptr) {
}
}
}Pointer Validation Recommendation
Each pointer must be validated, and there are certain rules to follow in order to get past static analysis tools flagging your code, and preventing it from getting merged.
// DO NOT DO THIS
// CERT C will flag this as 'warning 613: potential use of null pointer
void foo(module_s *module, void *pointer) {
const bool pointers_are_valid = (NULL != module) && (NULL != pointer);
if (pointers_are_valid) {
// ...
}
}
// Explicitly check pointers
void foo(module_s *module, void *pointer) {
if ((NULL != module) && (NULL != pointer)) {
// ...
}
}If there are "private" (or static) functions, they do not need redundant checks as long as code structure guarantee that they will never be called unsafely. These are "forgiven" by PCLint or CERTC as long as they follow the coding standards of containing __private_ name. Here is an example:
static void module__private_do(module_s *module, void *pointer) {
// No need to check 'if ((NULL != module) && (NULL != pointer))'
}
void module__do(module_s *module, void *pointer) {
if ((NULL != module) && (NULL != pointer)) {
module__private_do(module, pointer);
}
}Variables
Do not re-use variables, and avoid changing the variables passed as copy. You can opt to make the parameters const to enforce this.
sl_buffer_s sl_buffer__init(void *static_memory, size_t memory_size_bytes) {
memory_size_bytes = (NULL == static_memory) ? 0U : memory_size_bytes;
}
// Do this instead
sl_buffer_s sl_buffer__init(void *static_memory, size_t memory_size_bytes) {
const size_t sanity_checked_memory_size_bytes = (NULL == static_memory) ? 0U : memory_size_bytes;
}Data Initialization
Globals
C standards call for initialization of all global memory to zero, and therefore, there is no need for explicit initialization. For any customer integration that fails to do this, we need to work with them to make sure that their startup code performs this necessary step.
int my_global;
struct_s my_struct;Structs
Initialize the members by name. Use colon for C++ (it doesn’t support C dot initializer)
Example
const ecu_spi__channel_config_s ecu_spi__channel_configs[ECU_SPI__CHANNEL_COUNT] = {
[ECU_SPI__CHANNEL_MOTOR] = { .master = true, .timing = FALLING_EDGE},
[ECU_SPI__CHANNEL_HSD] = { .master = false, .timing = FALLING_EDGE},
[ECU_SPI__CHANNEL_SBC] = { .master = true, .timing = RISING_EDGE},
};For consistency reasons, when the dot initializer is not possible, then always use sl_utils__memset_zero(), then set the selected (or all) members appropriately.
void zero_struct_using_ptr(struct_s *s) {
sl_utils__memset_zero(s, sizeof(*s));
// Then
s->foo = 1;
}
void instantiate_struct() {
struct_s s;
sl_utils__memset_zero(&s, sizeof(s));
// Then
s.foo = 1;
}The reasons to use sl_utils__memset_zero() is that:
The notation
*s = (struct_s) { }is not portable because some compilers may complain about empty{ }memset()may be used but it creates possibility of bugs because the parameters of this function may be swapped easilyNote that
bzero()is not recommended since it has been deprecated
Do not confuse the struct initialization with array initialization which can use = {0}
char array[2] = {0}
Arrays/Maps
For constant arrays that map two sets of values and should contain a certain number of elements, use a static, compile-time assertion macro to validate the array size.
This ensures that the code will only compile when the number of elements in the map/array equals the number that is expected.
Example For an array that maps enums to strings:
static const sl_string_value_map_s image_type_string_map[] = {
{"BOOTLOADER", SL_ECU_INFO__IMAGE_TYPE_BOOTLOADER},
{"APP", SL_ECU_INFO__IMAGE_TYPE_APP},
{"UPDATER", SL_ECU_INFO__IMAGE_TYPE_UPDATER},
{"CALIBRATION_BLOCK", SL_ECU_INFO__IMAGE_TYPE_CALIBRATION_BLOCK},
};Consider adding the following compile-time assertion:
COMPILE_TIME_ASSERT((size_t)SL_ECU_INFO__IMAGE_TYPE_COUNT == ARRAY_COUNT(image_type_string_map));Safety and MISRA
No unreacheable code
You should not be able to do this at Sibros code base because of enforcement of 100% test coverage
No dead code
Do not leave commented code base that is not used in production
No recursive functions
In Embedded Systems, the stack memory is precious, as there is usually not very much.No dynamic memory allocation (MISRA C:2004, 20.4) i.e. you are not allowed to use malloc and free as it can cause unintended issues when functions such as malloc are unable to provide a valid memory location.
No continue or goto statements (MISRA C:2012, 15.1)
Because even the compiler itself cannot prevent stack corruption for this code:
goto jump_over;
int stack_corruption = 0;
jump_over:
stack_corruption = 123; // Oops, this was not allocated on the stackSwitch Statements
There should always be a default in the switch case. The default shall be the last statement in the switch block
Switch statements containing fall-through blocks require explicit comment acknowledging the intentional fall-through. To automate this, there is a
FALL_THROUGH_SWITCH_CASEmacro insl_common.hthat should be used to signal this intent.
Initialize all local variables - empty pointers should be initialized to
NULLInitialize arrays using:
int array[3] = { 0 };Do not do this:
int array[3] = { };
Prefer defining consts using this approach which is part of
<stdint.h>const uint32_t VALUE = UINT32_C (800000);Avoid doing this though:
UINT32_C(2 * 2)because this macro simply concatenatesULor similar based on the machine and the result of an operation occurring inside of the macro parameter may not be deterministic
Use parentheses to make code clear and explicit.
Do not do:
if a > b + c || d < eInstead do:
if ( (a > (b + c)) || (d < e) )
Conditional Operators Even one line statements should have curly brackets Example:
if (door_open) {
door_open = false;
}ifstatements should always have anelsestatement if there is anelse ifstatement. Theelsestatements help explicit indicate that we have thought about that situation and decided that no work needs to be done there. The compiler is smart and will optimize this code out anyway, so no need to worry that this takes code space. Pattern:
if (condition) {
// insert code here
} else if (second_condition) {
// insert some more code
} else {
}No unary operator if pre or post increment can alter the behavior of the code. Using these operators where there is only the increment or decrement operation is taking place is okay.
// Do not do this
*p++ = 1;
byte[counter++] = 123;
int b = a++;
int c = ++a;
if (++a > 5)
if (a++ > 5)
while (a++ > 6)
// This is okay
int okay = 5;
okay++;
okay = okay + 1;
okay += 1;
++okay;There should be no commented out code in the code base.
No
#if 0blocksNo block comments that disable code (source control is your friend)
This improves the readability of you code and reduces the amount of unnecessary code in the code base.
Unsigned numbers should have
Uat the end. Example:42UUnsigned long numbers should have
ULat the end. Example:56ULDo not write obfuscated 'smart' code - saving on the microseconds of processor time (or not even that) is not worth the headache of trying to understand someone else's tricky code which can take a long time and also be error prone.
Use static const in place of const for local variables:
Latest decision:
staticlocal variables are not allowed according to Module DesignThis rule caused confusion on the use of
static constThe benefit of variable data construction outweighs the cost of explanation of this rule
Obsolete decision:
For local variables, avoid using:
const char foo[] = “bar”const int test = 1Use
static const char foo[] = “bar”
Because if we use just const (without static), the variable will need to be constructed on each entry to the function, and would not only use extra runtime, but also use stack memory.
Do not use the C++ style Expects( ) and Ensures( )
We want our code to gracefully fail and not run into exceptions that it cannot handle. Whenever there is doubt, always assume you are designing a safety critical controller that is responsible to ensure that a car’s driver would always be able to apply the brakes correctly on the vehicle.
Documentation
Code should be self-documenting
Although, there may be code such as mathematical formulas that still require a comment to explain the intent of the code. In this case, make sure to write the comment at the time the code is written
Comments can be used to explain the intent of the code if self-documentation is not sufficient
Include all separator comment blocks (even when there is no content below them) such as:
Defines,typedefsetc.Please see coding file templates and example structure below
Use
/**to document multi-line comments for DoxygenFor consistency, header files (public APIs) should always do this over
/*This is for consistency and also because Lint/MISRA does not like multi-line comments using
//Documentation is mandatory for public facing interfaces and optional everywhere else.
See Doxygen Tags
Doxygen doesn’t like documenting
static inlinefunctions so do not use themThis is also discouraged because
inlinefunctions cannot be mocked during unit-testing, as mentioned previously
For consistency, always use only two types
/**style for functions or documentation prior to a construct///<for documenting something on the same line
Do not mix of
///and/**The header file looks more consistent this way
Do not use
@briefIt simply starts a new paragraph.
It has no effect on the documentation. If you want multiple paragraphs, separate them by an empty line
Do not use
@file my_file.hDoxygen knows your file's name so follow the
DRYprinciple
If there is no parameter or return type, then do not use
@param noneor@return nonerespectively@paramis only needed for parameter names that are not clearIf the variable is very clear and explicitly named, there should be no need for explaining the parameter. If someone does still explain the parameter for whatever reason, we don’t need to spend any time pointing it out and/or having them remove the comment or amend their commit (all of which takes more time than just leaving a redundant comment in there).
Avoid Comments - comments should only be used if your code itself cannot express the intent.
Truth can only be found in one place: the code.
-- Robert C. Martin, Clean Code
This means that the names of the variables and functions etc should describe their purpose instead of having to rely on comments to understand the code. Spend extra time choosing your variable names, and your goal should be that you do not need to document your APIs. Note that in this code sample, we do not need to describe the parameters.
/**
* Read up to 'bytes' number of bytes into the user provided memory
* @returns number of bytes consumed
*/
size_t sl_data_carrier__consume(sl_data_carrier_s *data_carrier, void *input_buffer, size_t bytes);Here is a bad example
uint32_t sl_crc32__compute(uint32_t crc, const uint8_t *data, size_t size, void (*callback)(void));
// But we can improve this by naming our variables that speak for the code itself
uint32_t sl_crc32__compute(uint32_t crc_initial_value, const uint8_t *data, size_t data_size_in_bytes,
void (*callback_during_computation)(void));No Redundant comments If the code can be read and interpreted, do not repeat that in a comment itself.
// This will call the close method and return
void close_file(file *file) {
close(file);
}No closing brace comments Refactor code such that the braces do not enclose a large block of code and hence there is no need for closing brace comments.
if (...) {
while (...) {
}
} // ifIn general, use comment block style comments (historic c-style) for consistency;
/* comment */.There should be absolutely no
TODOtags in the comments unless accompanied by a Gitlab ticketUse
/*style comments for multi-line comments (advised by Lint)Use
/**style comment to comment function docstrings (See Doxygen Comments)Do not use
/**style comments for documentation inside of functions (/**is for Doxygen)Use
/**<style comments to document struct members on the same line
Examples of good comments:
API documentation (doxygen style):
/**
* A brief stating the purpose of the function.
* @param some_parameter Explanation of parameter and it's value range.
* @return Explanation of what the function returns and expected value range.
*/
uint32_t my_function(uint32_t some_parameter);
/**
* Struct documentation
*/
typedef struct {
int member; /**< Documentation about this member */
} my_struct_s;Lint exception:
/*lint !e9087 memcpy() requires void *. */Implementation details that may not be obvious to other developers:
void mcu_reset__perform_reset(void) {
/* Unlock is required before writing to the SCU registers to request reset */
IfxScuWdt_clearSafetyEndinitInline(IfxScuWdt_getSafetyWatchdogPasswordInline());
IfxCpu_triggerSwReset();
}Commenting with ticket for future improvement:
void os_system_config__shutdown(os_system__shutdown_reason_e shutdown_reason) {
(void)shutdown_reason; //TODO #1234: Need to update shared ram
}Comments needing to be resolved before merging:
Temporary test code:
// TESTING
if (main_test.timeout > 0) {An incomplete implementation (resolve before merging, otherwise provide ticket #):
void os_system_config__shutdown(os_system__shutdown_reason_e shutdown_reason) {
(void)shutdown_reason; //TODO: Need to update shared ram
}Balanced length for variables
Keep a balance between variable length and making the intent clear.
Code completion can help
Avoid exceeding the line character limit
Do not use a very long name such as:
SL_UDS_SERVICE_SECURITY_ACCESS_DIFFERENCE_BETWEEN_REQUEST_SEED_AND_SEND_KEY
Use this instead:
SL_UDS_SERVICE_SECURITY_ACCESS_SEED_TO_KEY_OFFSET
Avoid magic numbers
The definition of a magic number is when a hardcoded constant in code is obscure, and the developer's intent is not clear. In other words, readers may ask why was this specific constant chosen? And, what are the effects if I modify the constant?
Developers should avoid magic numbers by ensuring that the intent of any hardcoded constant is clear.
Timeout example
// Good: Timeout is some arbitrary whole unit (1 second)
const int tcp_connect_timeout_ms = 1000;
TCP_connect(&connection, tcp_connect_timeout_ms);
// Bad: This timeout is suspiciously specific
// Is it derived from a calibration? Or, is it random?
const int tcp_connect_timeout_ms = 3655;
TCP_connect(&connection, tcp_connect_timeout_ms);Error code example
// Bad: What is the significance of 3? What about other constants like 2 or 4?
int error_code = TCP_connect(&connection, timeout_ms);
if (3 == error_code) {
// Do error handling logic
}
// Good: Intent is clear; the if condition handles a timeout error
error_code_e error_code = TCP_connect(&connection, timeout_ms);
if (ERROR_CODE__TIMEOUT == error_code) {
// Do error handling logic
}Size example
SIBROS TECHNOLOGIES, INC. CONFIDENTIAL