# 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 ` - 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 #include "my_custom_header.h" /* File content here */ #endif /* TEMPLATE_HDR_H */ ```