Files

531 lines
13 KiB
Markdown

# C Style Guide and Rules
## Table of contents
- [General rules](#general-rules)
- [Comments](#comments)
- [Functions](#functions)
- [Structures, enumerations, typedefs](#structures-enumerations-typedefs)
- [Compound statements](#compound-statements)
- [Macros and preprocessor directives](#macros-and-preprocessor-directives)
- [Documentation](#documentation)
- [Header/source files](#headersource-files)
## Conventions used
MUST, MUST NOT, SHOULD, SHOULD NOT, MAY and OPTIONAL are used as described in [RFC 2119](https://tools.ietf.org/html/rfc2119).
## General rules
- Do not use tabs, use spaces instead
- Use `4` spaces per indent level
- Use `1` space between keyword and opening bracket
```c
// OK
if (condition)
while (condition)
for (init; condition; step)
do {} while (condition)
// Wrong
if(condition)
while(condition)
for(init;condition;step)
do {} while(condition)
```
- Limit the line length to `120` characters, use `120` characters as a soft limit.
- Do not use space between function name and opening bracket
```c
int32_t a = sum(4, 3); // OK
int32_t a = sum (4, 3); // Wrong
```
- Use only lowercase characters for variables/functions/types with optional underscore `_` char
- Opening curly bracket is always at the same line as keyword (`for`, `while`, `do`, `switch`, `if`, ...)
```c
size_t i;
for (i = 0; i < 5; ++i) { // OK
}
for (i = 0; i < 5; ++i){ // Wrong
}
for (i = 0; i < 5; ++i) // Wrong
{
}
```
- Use single space before and after comparison and assignment operators
```c
int32_t a;
a = 3 + 4; // OK
for (a = 0; a < 5; ++a) // OK
a=3+4; // Wrong
a = 3+4; // Wrong
for (a=0;a<5;++a) // Wrong
```
- Use single space after every comma
```c
func_name(5, 4); // OK
func_name(4,3); // Wrong
```
- Do not initialize `global` variables to any default value (or `NULL`), implement it in the dedicated `init` function (if REQUIRED).
```c
static int32_t a; // OK
static int32_t b = 4; // Wrong
void my_module_init(void) {
a = 0;
b = 4;
}
```
- Declare local variables in order
1. Custom types, structures and enumerations
2. Integer types
3. Single/Double floating point
```c
int my_func(void) {
// 1
my_struct_t my;
my_struct_ptr_t* p;
// 2
uint32_t a;
char h;
// 3
double d;
float f;
}
```
- Always declare local variables at the beginning of the block, before the first executable statement
- Always add trailing comma in the last element of structure (or its children) initialization. Unless the structure is very simple and short
```c
typedef struct {
int a, b;
} str_t;
str_t s = {
.a = 1,
.b = 2, // Comma here
};
// No trailing commas - only for small and simple structures
static const str_t y = {.a = 1, .b = 2};
```
- Declare counter variables in `for` loop
```c
// OK
for (size_t i = 0; i < 10; ++i)
// OK, if you need the counter variable later
size_t i;
for (i = 0; i < 10; ++i) {
if (...) {
break;
}
}
if (i == 10) {
}
// Wrong
size_t i;
for (i = 0; i < 10; ++i) ...
```
- Except `char`, `float` or `double`, always use types declared in `stdint.h` library, eg. `uint8_t` for `unsigned 8-bit`, etc.
- Never compare against `true`, eg. `if (check_func() == 1)`, use `if (check_func()) { ... }`
- Always compare pointers against `NULL` value
```c
void* ptr;
// OK, compare against NULL
if (ptr == NULL || ptr != NULL) {
}
// Wrong
if (ptr || !ptr) {
}
```
- Always use `size_t` for length or size variables
- Always use `const` for a pointer if function should not modify memory pointed to by `pointer`
- Always use `const` for a function parameter or variable, if it should not be modified
```c
/**
* When d could be modified, data pointed to by d could not be modified
*/
void my_func(const void* d) {
}
/**
* When d and data pointed to by d both could not be modified
*/
void my_func(const void* const d) {
}
/**
* When d should not be modified inside function, only data pointed to by d could be modified
*/
void my_func(void* const d) {
}
```
- When a function may accept a pointer of any type, always use `void *`, do not use `uint8_t *`
- The function MUST take care of proper casting in implementation
- Always use brackets with `sizeof` operator
- Always compare variable against zero, except if it is treated as `boolean` type
- Never compare `boolean-treated` variables against zero or one. Use NOT (`!`) instead
```c
size_t length = 5; // Counter variable
uint8_t is_ok = 0; // Boolean-treated variable
if (length) // Wrong, length is not treated as boolean
if (is_ok) // OK, variable is treated as boolean
if (!is_ok) // OK
if (is_ok == 1) // Wrong, never compare boolean variable against 1!
if (is_ok == 0) // Wrong, use ! for negative check
```
- Every function MUST include *doxygen-enabled* comment, even if function is `static`
- Use English names/text for functions, variables, comments
- Use *lowercase* characters for variables
- Use *underscore* if variable contains multiple names, eg. `force_redraw`. Do not use `forceRedraw`
- Always use `<` and `>` for C Standard Library include files, eg. `#include <stdlib.h>`
- Always use `""` for custom libraries, eg. `#include "my_library.h"`
- When casting to a pointer type, always align the asterisk to the type, eg. `uint8_t* t = (uint8_t*)var_width_diff_type`
## Comments
- Start every comment with capital letter, if possible
- Use `//` for single-line comments, start comment with single space
```c
// This is comment (ok)
/* This is comment (wrong) */
```
- For multi-line comments use `space+asterisk` for every line
```c
/*
* This is multi-line comments,
* written in 2 lines (ok)
*/
/**
* Wrong, use double-asterisk only for doxygen documentation
*/
/*
* Single line comment without space before asterisk (wrong)
*/
/*
* Single line comment in multi-line configuration (wrong)
*/
```
- Make comments aligned to 4-spaces indent from the beginning of line, and aligned with the rest of the block
```c
void my_func(void) {
char a, b;
a = call_func_returning_char_a(a); // This is a comment
b = call_func_returning_char_b_longer_func(a); // This is a comment, aligned to 4-spaces indent
}
```
## Functions
- Every function which may have access from outside its module, must include function *prototype* (or *declaration*) in the header file
- Every function that is used only inside its module:
- Must be declared as `static`
- Must NOT include function prototype in the header file
- Must include function prototype in the source file
- Function name MUST be lowercase, optionally separated with underscore `_` character
```c
// OK
void my_func(void);
void myfunc(void);
// Wrong
void MYFunc(void);
void myFunc();
```
- When function returns pointer, align the asterisk to the return type
```c
// OK
const char* my_func(void);
my_struct_t* my_func(int32_t a, int32_t b);
// Wrong
const char *my_func(void);
my_struct_t * my_func(void);
```
## Structures, enumerations, typedefs
- Structure or enumeration name MUST be lowercase with optional underscore `_` character between words
- Structure or enumeration may contain `typedef` keyword
- All structure members SHOULD be lowercase
- All enumeration members MUST be uppercase
When structure is declared, it may use one of `3` different options:
1. When structure is declared with *name only*, it *MUST not* contain `_t` suffix after its name.
```c
struct struct_name {
char* a;
char b;
};
```
2. When structure is declared with *typedef only*, it *has to* contain `_t` suffix after its name.
```c
typedef struct {
char* a;
char b;
} struct_name_t;
```
3. When structure is declared with *name and typedef*, it *MUST NOT* contain `_t` for basic name and it *MUST* contain `_t` suffix after its name for typedef part.
```c
typedef struct struct_name {
char* a;
char b;
char c;
} struct_name_t;
```
- When new typedef is introduced for function handles, use `_fn` suffix
```c
typedef uint8_t (*my_func_typedef_fn)(uint8_t p1, const char* p2);
```
## Compound statements
- Every compound statement MUST include opening and closing curly bracket, even if it includes only `1` nested statement
- Every compound statement MUST include single indent; when nesting statements, include `1` indent size for each nest
```c
// OK
if (c) {
do_a();
} else {
do_b();
}
// Wrong
if (c)
do_a();
else
do_b();
// Wrong
if (c) do_a();
else do_b();
```
- In case of `if` or `if-else-if` statement, `else` MUST be in the same line as closing bracket of the first statement
```c
// OK
if (a) {
} else if (b) {
} else {
}
// Wrong
if (a) {
}
else {
}
// Wrong
if (a) {
}
else
{
}
```
- In case of `do-while` statement, `while` part MUST be in the same line as closing bracket of `do` part
```c
// OK
do {
/* ... */
} while (check());
// Wrong
do
{
/* ... */
} while (check());
// Wrong
do {
/* ... */
}
while (check());
```
- Compound statement MUST include curly brackets, even in the case of a single statement.
- Avoid incrementing variables inside loop block if possible.
### Switch statement
- Always include `default` statement
- Add *single indent* for every `case` statement
```c
// Ok
switch (check()) {
case 0:
do_a();
break;
case 1:
do_b();
break;
default:
break;
}
// Wrong
switch (check()) {
case 0: // Wrong, case indent missing
do_a();
break; // Wrong, break MUST have indent as it is under case
case 1:
do_b(); // Wrong, indent under case is missing
break;
default:
break;
}
```
## Macros and preprocessor directives
- Always use macros instead of literal constants, especially for numbers
- Always protect input parameters with parentheses
- All macros MUST be fully uppercase, with optional underscore `_` character between words
```c
// OK
#define SQUARE(x) ((x) * (x))
// Wrong
#define square(x) ((x) * (x))
// Wrong
#define MIN(x, y) x < y ? x : y
```
- - Always document `if/elif/else/endif/ifdef/ifndef` statements
```c
#ifdef XYZ
/* do something */
#endif /* XYZ */
```
- Do not indent sub statements inside `#if` statement
## Documentation
- Use doxygen-enabled documentation style for `variables`, `functions` and `structures/enumerations`
- Always use `@` for doxygen, do not use `\`
```c
/**
* @brief The main screen
*/
lv_obj_t * main_screen;
```
- Every structure/enumeration member MUST include documentation
```c
/**
* @brief Struct containing information about a wifi network
* @note The ssid is null terminated
*/
typedef struct {
char ssid[33];
int8_t rssi;
ui_wifi_auth_mode_t auth_mode;
} ui_wifi_network_t;
```
- Documentation for functions used outside its module MUST be placed above function prototype in header file, that is, documentation that describes WHAT the function does.
- Documentation that describes HOW the function does it be placed with the function implementation.
- For functions that have only internal use, documentation MUST be placed with the function implementation.
- All functions MUST include a `brief` description
- Every parameter MUST be noted if it is `in` or `out` for *input* and *output* respectively
- Function MUST include `return` parameter if it returns something. This does not apply for `void` functions
- Function can include other doxygen keywords, such as `note` or `warning`
- If function returns member of enumeration, use `ref` keyword to specify which one
```c
/**
* @brief Parse and publish data from the meter data history endpoint
*
* @param[in] buf The buffer containing the JSON data
* @param[in] len The length of the JSON data
* @return @ref ESP_OK on success, member of @ref esp_err_t otherwise
*/
static esp_err_t parse_publish_meter_data_history(uint8_t *buf, uint32_t len) {
/* ... */
}
```
## Header/source files
- Leave a single empty line at the end of the file
- Every file MUST include doxygen annotation for `file` and `brief` description followed by empty line
```c
/**
* @file tsc2046.c
* @brief TSC2046 touch screen controller driver
*/
// Empty line
```
- Header file MUST include guard `#ifndef`
- Include external header files with STL C files first followed by application custom files
- Header file MUST include only every other header file to compile correctly, but not more (.c should include the rest if REQUIRED)
- Header file MUST only expose module public variables/types/functions
- Use `extern` for global module variables in header file, define them in source file later
```
/* file.h ... */
#ifndef ...
extern int32_t my_variable; // This is global variable declaration in header
#endif
/* file.c ... */
int32_t my_variable; // Actually defined in source
```
- Never include `.c` files in another `.c` file
- Do not include module private declarations in header file
- Header file example
```c
/* License comes here */
#ifndef TEMPLATE_HDR_H
#define TEMPLATE_HDR_H
/* Include headers */
#include <stdint.h>
#include "my_custom_header.h"
/* File content here */
#endif /* TEMPLATE_HDR_H */
```