|
|
|
# Table of Contents
|
|
|
|
|
|
|
|
<details>
|
|
|
|
<summary>
|
|
|
|
Click to expand
|
|
|
|
</summary>
|
|
|
|
|
|
|
|
[[_TOC_]]
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
----
|
|
# Embedded C Coding Standards
|
|
# Embedded C Coding Standards
|
|
|
|
|
|
**Goals**
|
|
**Goals**
|
... | @@ -21,11 +33,8 @@ Any code you write, modify or generate should follow the coding standards. If y |
... | @@ -21,11 +33,8 @@ Any code you write, modify or generate should follow the coding standards. If y |
|
|
|
|
|
----
|
|
----
|
|
|
|
|
|
[[_TOC_]]
|
|
|
|
|
|
|
|
----
|
|
|
|
|
|
|
|
# Best Practices
|
|
# Best Practices
|
|
|
|
|
|
## Principles
|
|
## 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.
|
|
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.
|
... | @@ -140,6 +149,11 @@ Here are some general Sibros guidelines for being a good, responsible engineer. |
... | @@ -140,6 +149,11 @@ Here are some general Sibros guidelines for being a good, responsible engineer. |
|
* Memory allocation should never fail
|
|
* 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
|
|
* 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
|
|
|
|
|
|
## Modularize Code
|
|
## 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.
|
|
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.
|
|
|
|
|
... | @@ -152,36 +166,63 @@ If we are creating an HTTP client, do not put all the code into a single module. |
... | @@ -152,36 +166,63 @@ If we are creating an HTTP client, do not put all the code into a single module. |
|
Reference [this article](https://gitlab.com/sibros_public/public/wikis/c/code_designs#code-modularization) for further inspiration.
|
|
Reference [this article](https://gitlab.com/sibros_public/public/wikis/c/code_designs#code-modularization) for further inspiration.
|
|
|
|
|
|
----
|
|
----
|
|
|
|
# Thoughts and Decisions
|
|
|
|
## C vs. C++
|
|
|
|
|
|
|
|
TODO: Move this section to a separate file
|
|
|
|
|
|
|
|
C++ should ideally be used for all new projects, but the truth is that many companies are still in the C land. There is no question that C++ makes things better for the programmer, and there are potentially fewer mistakes the language allows, although on the contrary:
|
|
|
|
|
|
|
|
> C makes it easy to shoot yourself in the foot. C++ makes it harder, but when you do, you blow away your whole leg!
|
|
|
|
>
|
|
|
|
>—Bjarne Stroustrup, Bjarne Stroustrup’s FAQ: Did you really say that?
|
|
|
|
|
|
|
|
Other thoughtful points:
|
|
|
|
|
|
|
|
* For any libraries that we provide to customers, we should do it in C
|
|
|
|
* For any internal libraries, we could consider using C++
|
|
|
|
* C++ requires a different skill-set and more discipline, so we should only use C++ when
|
|
|
|
appropriate
|
|
|
|
|
|
|
|
|
|
|
|
## Relative Includes vs. Explicit Path includes
|
|
|
|
|
|
|
|
There are two ways to design a code module:
|
|
|
|
```c
|
|
|
|
// Option 1
|
|
|
|
#include "sl_data_carrier.h"
|
|
|
|
```
|
|
|
|
|
|
|
|
```c
|
|
|
|
// Option 2
|
|
|
|
#include "../buffers/sl_data_carrier.h"
|
|
|
|
```
|
|
|
|
|
|
# Fundamentals
|
|
Option 1: Compiler `-I` path resolution
|
|
|
|
|
|
## Snake Case
|
|
* `#includes` are easier to maintain
|
|
|
|
* Immune to code module path changes
|
|
|
|
|
|
After some research, we decided to go with `snake_case` or the `underscore` convention, primarily due to the following reasons:
|
|
Option 2: Relative Includes
|
|
|
|
|
|
|
|
* Avoids ambiguous includes
|
|
|
|
* Avoids the customer from having to `-I` all include files
|
|
|
|
|
|
* Snake case does not suffer from
|
|
In the first example, we expect that the code integrator (customer) has resolved the paths to be able to build the code module. In the second example, we explicitly rely on a folder structure to pull in appropriate header files.
|
|
* Differentiating Abbreviations `tcpIpOpen`
|
|
|
|
* [I hate Camel Case](https://yosefk.com/blog/ihatecamelcase.html)
|
|
|
|
* Naming convention inside code can match the file names
|
|
|
|
* Use `myCodeModule.c` creates issues with diverse build systems (Windows vs. Linux)
|
|
|
|
|
|
|
|
Therefore, use `snake_case` with no capital letters for all code, file and folder names
|
|
**We chose to go with `Option 1` because:**
|
|
* Do not use `camelCase`
|
|
|
|
* Do not use `PascalCase`
|
|
|
|
* Do not use Hungarian notation (like `pMyString` for pointers, 'bSwitch' for booleans)
|
|
|
|
|
|
|
|
### Names of basic types and code modules
|
|
* If files move around, we will not have to change code dependencies
|
|
* Use `ALL_CAPS_SNAKE_CASE` for
|
|
* If there are conflicts to file includes (customer `common.h` vs. our `common.h`) then we have plans to mitigate the problem
|
|
* Global Constants (local variables that are const can be lowercase to differentiate between a global vs. local variable)
|
|
* Our code modules have layer names, and the chance of conflict are minimal .i.e: `sl_common.h`
|
|
* Enumeration values
|
|
* One time software integration offsets the cost of relative includes
|
|
* Code Module
|
|
----
|
|
* Precede with layer name (see section below)
|
|
# Structure
|
|
* Create a header file, source file, and its corresponding unit-test file
|
|
|
|
* If there are private enums, typedefs, or functions, then also create the private module
|
|
|
|
* Example: `app_my_module.h`, `app_my_module.c`, `app_my_module_private.h`, `test_app_my_module.c`
|
|
|
|
|
|
|
|
Check more details at [Naming Conventions](#naming-convention)
|
|
## Whitespace & Code Format
|
|
|
|
Newer languages like Golang have whitespace rules, such as the brace positions built into the language. We chose to implement same idea by using `clang-format`, which auto formats the source code according to pre-set rules such that **we focus our code reviews on the actual matter, and not the code format**.
|
|
|
|
|
|
|
|
You have no choice for the whitespace format, such as tabs vs. spaces or position of the curly braces etc. because `clang-format` program will auto-format your code during `git commit`. Google C++ code **whitespace** formatting rules are used to format code during a `post-commit` hook. The only exception to the Google rules is that we use **120** character limit for lines instead of 80 characters.
|
|
|
|
|
|
## Layers
|
|
## Layers
|
|
|
|
|
... | @@ -201,50 +242,6 @@ The layers are: |
... | @@ -201,50 +242,6 @@ The layers are: |
|
|
|
|
|
`sl` is a code module that can be compiled independently of others. It has no linker dependency on another module, such as `sl_crc32.c`. However, it is okay if it uses function callbacks.
|
|
`sl` is a code module that can be compiled independently of others. It has no linker dependency on another module, such as `sl_crc32.c`. However, it is okay if it uses function callbacks.
|
|
|
|
|
|
## Types
|
|
|
|
|
|
|
|
We should be able to decipher the type looking at the name of the type. Therefore, follow the following naming convention for new types. Read more details at [Constructs section](#standards-for-constructs)
|
|
|
|
|
|
|
|
* Enums: `typedef enum enum_e;`
|
|
|
|
* Structs: `typedef struct struct_s;`
|
|
|
|
* Unions: `typedef union union_u;`
|
|
|
|
* Type: `typedef old_type new_type_t;`
|
|
|
|
* Function Pointer: `typedef void (*func_ptr_f)(void);`
|
|
|
|
|
|
|
|
Avoid obscuring a type, for example: `typedef char hostname[32];`. This makes the code unsafe because an aribtrary char array can get past the compiler checks:
|
|
|
|
```c
|
|
|
|
void set_default_hostname(hostname name) {
|
|
|
|
strncpy(name, "sibros", sizeof(name) - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void oops(void) {
|
|
|
|
char hostname[2];
|
|
|
|
set_default_hostname(hostname);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
You can make this code safer by using a data structure which will avoid the `oops()` function above.
|
|
|
|
```c
|
|
|
|
typedef struct {
|
|
|
|
char string[32];
|
|
|
|
} hostname_s;
|
|
|
|
```
|
|
|
|
|
|
|
|
## Preferred Types
|
|
|
|
|
|
|
|
### Use `<stdint.h>`
|
|
|
|
* Avoid using `int` or `long` because they may not port across CPUs with different bit-width
|
|
|
|
* Instead use explicit `uint32_t` or `int64_t` etc.
|
|
|
|
* Do not define your own bool (examples: BOOL, _bool, bool_t), use `#include <stdbool.h>` instead
|
|
|
|
|
|
|
|
### Use `size_t`
|
|
|
|
* Use `size_t` for any "size types" of unsigned numbers
|
|
|
|
* Use for indexes, and container sizes, much like the C++ library
|
|
|
|
* Do not use `size_t` when you want the size to be explicit
|
|
|
|
* Example: `uint32_t` for a 32-bit address of SPI flash
|
|
|
|
* Use `ssize_t` for any signed types
|
|
|
|
|
|
|
|
|
|
|
|
## Double Underscore
|
|
## Double Underscore
|
|
|
|
|
|
Double underscores are used to quickly distinguish what layer and module something belongs to. Please look at the diagram below and the explanations that follow afterwards.
|
|
Double underscores are used to quickly distinguish what layer and module something belongs to. Please look at the diagram below and the explanations that follow afterwards.
|
... | @@ -304,10 +301,6 @@ typedef struct { |
... | @@ -304,10 +301,6 @@ typedef struct { |
|
void sl_queue__init(sl_queue_s *queue);
|
|
void sl_queue__init(sl_queue_s *queue);
|
|
```
|
|
```
|
|
|
|
|
|
## Whitespace & Code Format
|
|
|
|
Newer languages like Golang have whitespace rules, such as the brace positions built into the language. We chose to implement same idea by using `clang-format`, which auto formats the source code according to pre-set rules such that **we focus our code reviews on the actual matter, and not the code format**.
|
|
|
|
|
|
|
|
You have no choice for the whitespace format, such as tabs vs. spaces or position of the curly braces etc. because `clang-format` program will auto-format your code during `git commit`. Google C++ code **whitespace** formatting rules are used to format code during a `post-commit` hook. The only exception to the Google rules is that we use **120** character limit for lines instead of 80 characters.
|
|
|
|
|
|
|
|
## Include Guard
|
|
## Include Guard
|
|
|
|
|
... | @@ -428,64 +421,34 @@ The order of includes is just like an ordinary header file, except that we inclu |
... | @@ -428,64 +421,34 @@ The order of includes is just like an ordinary header file, except that we inclu |
|
#include "sl_unit_test_facilitator.h" // or this
|
|
#include "sl_unit_test_facilitator.h" // or this
|
|
```
|
|
```
|
|
|
|
|
|
----
|
|
|
|
|
|
|
|
# Thoughts and Decisions
|
|
|
|
## C vs. C++
|
|
|
|
|
|
|
|
TODO: Move this section to a separate file
|
|
|
|
|
|
|
|
C++ should ideally be used for all new projects, but the truth is that many companies are still in the C land. There is no question that C++ makes things better for the programmer, and there are potentially fewer mistakes the language allows, although on the contrary:
|
|
|
|
|
|
|
|
> C makes it easy to shoot yourself in the foot. C++ makes it harder, but when you do, you blow away your whole leg!
|
|
|
|
>
|
|
|
|
>—Bjarne Stroustrup, Bjarne Stroustrup’s FAQ: Did you really say that?
|
|
|
|
|
|
|
|
Other thoughtful points:
|
|
|
|
|
|
|
|
* For any libraries that we provide to customers, we should do it in C
|
|
|
|
* For any internal libraries, we could consider using C++
|
|
|
|
* C++ requires a different skill-set and more discipline, so we should only use C++ when
|
|
|
|
appropriate
|
|
|
|
|
|
|
|
|
|
|
|
## Relative Includes vs. Explicit Path includes
|
|
|
|
|
|
|
|
There are two ways to design a code module:
|
|
|
|
```c
|
|
|
|
// Option 1
|
|
|
|
#include "sl_data_carrier.h"
|
|
|
|
```
|
|
|
|
|
|
|
|
```c
|
|
|
|
// Option 2
|
|
|
|
#include "../buffers/sl_data_carrier.h"
|
|
|
|
```
|
|
|
|
|
|
|
|
Option 1: Compiler `-I` path resolution
|
|
|
|
|
|
|
|
* `#includes` are easier to maintain
|
|
|
|
* Immune to code module path changes
|
|
|
|
|
|
|
|
Option 2: Relative Includes
|
|
|
|
|
|
|
|
* Avoids ambiguous includes
|
|
|
|
* Avoids the customer from having to `-I` all include files
|
|
|
|
|
|
|
|
In the first example, we expect that the code integrator (customer) has resolved the paths to be able to build the code module. In the second example, we explicitly rely on a folder structure to pull in appropriate header files.
|
|
|
|
|
|
|
|
**We chose to go with `Option 1` because:**
|
|
|
|
|
|
|
|
* If files move around, we will not have to change code dependencies
|
|
|
|
* If there are conflicts to file includes (customer `common.h` vs. our `common.h`) then we have plans to mitigate the problem
|
|
|
|
* Our code modules have layer names, and the chance of conflict are minimal .i.e: `sl_common.h`
|
|
|
|
* One time software integration offsets the cost of relative includes
|
|
|
|
|
|
|
|
|
|
|
|
----
|
|
----
|
|
|
|
|
|
# Naming Convention
|
|
# Naming Convention
|
|
|
|
* **Snake Case**
|
|
|
|
|
|
|
|
* After some research, we decided to go with `snake_case` or the `underscore` convention, primarily due to the following reasons:
|
|
|
|
|
|
|
|
* Snake case does not suffer from:
|
|
|
|
* Differentiating Abbreviations `tcpIpOpen`
|
|
|
|
* [I hate Camel Case](https://yosefk.com/blog/ihatecamelcase.html)
|
|
|
|
* Naming convention inside code can match the file names
|
|
|
|
* Use `myCodeModule.c` creates issues with diverse build systems (Windows vs. Linux)
|
|
|
|
|
|
|
|
* Therefore, use `snake_case` with no capital letters for all code, file and folder names
|
|
|
|
* Do not use `camelCase`
|
|
|
|
* Do not use `PascalCase`
|
|
|
|
* Do not use Hungarian notation (like `pMyString` for pointers, 'bSwitch' for booleans)
|
|
|
|
|
|
|
|
* **Names of basic types and code modules**
|
|
|
|
* Use `ALL_CAPS_SNAKE_CASE` for
|
|
|
|
* Global Constants (local variables that are const can be lowercase to differentiate between a global vs. local variable)
|
|
|
|
* Enumeration values
|
|
|
|
* Code Module
|
|
|
|
* Precede with layer name
|
|
|
|
* Create a header file, source file, and its corresponding unit-test file
|
|
|
|
* If there are private enums, typedefs, or functions, then also create the private module
|
|
|
|
* Example: `app_my_module.h`, `app_my_module.c`, `app_my_module_private.h`, `test_app_my_module.c`
|
|
|
|
|
|
* **Acronyms in lowercase**
|
|
* **Acronyms in lowercase**
|
|
- All acronyms shall be lowercase, just like all other names, whether they are part of functions, variables, or any other types
|
|
- All acronyms shall be lowercase, just like all other names, whether they are part of functions, variables, or any other types
|
|
- The reason for this is to keep naming consistent with the file naming convention, which uses all lowercase
|
|
- The reason for this is to keep naming consistent with the file naming convention, which uses all lowercase
|
... | @@ -609,7 +572,7 @@ uint32_t sl_crc32__compute(uint32_t crc_initial_value, const uint8_t *data, size |
... | @@ -609,7 +572,7 @@ uint32_t sl_crc32__compute(uint32_t crc_initial_value, const uint8_t *data, size |
|
|
|
|
|
## Bad Comments
|
|
## Bad Comments
|
|
|
|
|
|
Must of this comes from `Clean Code`:
|
|
Most of this comes from `Clean Code`:
|
|
|
|
|
|
**Mumbling**
|
|
**Mumbling**
|
|
If you do have a comment, make sure it is written with correct grammar and expresses an intent to make it useful.
|
|
If you do have a comment, make sure it is written with correct grammar and expresses an intent to make it useful.
|
... | @@ -671,11 +634,54 @@ void my_module__perform_operation(); |
... | @@ -671,11 +634,54 @@ void my_module__perform_operation(); |
|
// What type of info?
|
|
// What type of info?
|
|
void my_module__update_info();
|
|
void my_module__update_info();
|
|
```
|
|
```
|
|
|
|
---
|
|
|
|
|
|
----
|
|
|
|
|
|
|
|
# Standards for Constructs
|
|
# Standards for Constructs
|
|
|
|
|
|
|
|
## Types
|
|
|
|
|
|
|
|
We should be able to decipher the type looking at the name of the type. Therefore, follow the following naming convention for new types.
|
|
|
|
|
|
|
|
* Enums: `typedef enum enum_e;`
|
|
|
|
* Structs: `typedef struct struct_s;`
|
|
|
|
* Unions: `typedef union union_u;`
|
|
|
|
* Type: `typedef old_type new_type_t;`
|
|
|
|
* Function Pointer: `typedef void (*func_ptr_f)(void);`
|
|
|
|
|
|
|
|
Avoid obscuring a type, for example: `typedef char hostname[32];`. This makes the code unsafe because an aribtrary char array can get past the compiler checks:
|
|
|
|
```c
|
|
|
|
void set_default_hostname(hostname name) {
|
|
|
|
strncpy(name, "sibros", sizeof(name) - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void oops(void) {
|
|
|
|
char hostname[2];
|
|
|
|
set_default_hostname(hostname);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
You can make this code safer by using a data structure which will avoid the `oops()` function above.
|
|
|
|
```c
|
|
|
|
typedef struct {
|
|
|
|
char string[32];
|
|
|
|
} hostname_s;
|
|
|
|
```
|
|
|
|
|
|
|
|
## Preferred Types
|
|
|
|
|
|
|
|
### Use `<stdint.h>`
|
|
|
|
* Avoid using `int` or `long` because they may not port across CPUs with different bit-width
|
|
|
|
* Instead use explicit `uint32_t` or `int64_t` etc.
|
|
|
|
* Do not define your own bool (examples: BOOL, _bool, bool_t), use `#include <stdbool.h>` instead
|
|
|
|
|
|
|
|
### Use `size_t`
|
|
|
|
* Use `size_t` for any "size types" of unsigned numbers
|
|
|
|
* Use for indexes, and container sizes, much like the C++ library
|
|
|
|
* Do not use `size_t` when you want the size to be explicit
|
|
|
|
* Example: `uint32_t` for a 32-bit address of SPI flash
|
|
|
|
* Use `ssize_t` for any signed types
|
|
|
|
|
|
## Functions
|
|
## Functions
|
|
* **Public functions** should be separated from the layer and the module name with two underscores.
|
|
* **Public functions** should be separated from the layer and the module name with two underscores.
|
|
* Pattern: `<layer>_<module>__api_name();`
|
|
* Pattern: `<layer>_<module>__api_name();`
|
... | @@ -1494,5 +1500,4 @@ TODO: |
... | @@ -1494,5 +1500,4 @@ TODO: |
|
|
|
|
|
```c
|
|
```c
|
|
// TODO: Perfect example with most of the code constructs (unions, structs) in use
|
|
// TODO: Perfect example with most of the code constructs (unions, structs) in use
|
|
```
|
|
``` |
|
|
|
\ No newline at end of file |