Files
2023-Webservices_And_Applic…/style_guide.md
2023-10-23 21:09:32 +02:00

13 KiB

C Style Guide and Rules

Table of contents

Conventions used

MUST, MUST NOT, SHOULD, SHOULD NOT, MAY and OPTIONAL are used as described in RFC 2119.

General rules

  • Do not use tabs, use spaces instead
  • Use 4 spaces per indent level
  • Use 1 space between keyword and opening bracket
// 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
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, ...)
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
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
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).
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
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
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
// 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
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

/** 
 * 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
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
// This is comment (ok)
/* This is comment (wrong) */
  • For multi-line comments use space+asterisk for every line
/*
 * 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
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
// 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
// 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.
struct struct_name {
    char* a;
    char b;
};
  1. When structure is declared with typedef only, it has to contain _t suffix after its name.
typedef struct {
    char* a;
    char b;
} struct_name_t;
  1. 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.
typedef struct struct_name {
    char* a;
    char b;
    char c;
} struct_name_t;
  • When new typedef is introduced for function handles, use _fn suffix
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
// 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
// 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
// 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
// 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
// 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
#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 \
/**
 * @brief The main screen
 */
lv_obj_t * main_screen;
  • Every structure/enumeration member MUST include documentation
/**
 * @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
/**
 * @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
/**
 * @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

/* 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 */