13 KiB
13 KiB
C Style Guide and Rules
Table of contents
- General rules
- Comments
- Functions
- Structures, enumerations, typedefs
- Compound statements
- Macros and preprocessor directives
- Documentation
- Header/source files
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
4spaces per indent level - Use
1space 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
120characters, use120characters 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
globalvariables to any default value (orNULL), implement it in the dedicatedinitfunction (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
- Custom types, structures and enumerations
- Integer types
- 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
forloop
// 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,floatordouble, always use types declared instdint.hlibrary, eg.uint8_tforunsigned 8-bit, etc. - Never compare against
true, eg.if (check_func() == 1), useif (check_func()) { ... } - Always compare pointers against
NULLvalue
void* ptr;
// OK, compare against NULL
if (ptr == NULL || ptr != NULL) {
}
// Wrong
if (ptr || !ptr) {
}
- Always use
size_tfor length or size variables - Always use
constfor a pointer if function should not modify memory pointed to bypointer - Always use
constfor 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 useuint8_t *- The function MUST take care of proper casting in implementation
- Always use brackets with
sizeofoperator - Always compare variable against zero, except if it is treated as
booleantype - Never compare
boolean-treatedvariables 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 useforceRedraw - 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+asteriskfor 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
- Must be declared as
- 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
typedefkeyword - All structure members SHOULD be lowercase
- All enumeration members MUST be uppercase
When structure is declared, it may use one of 3 different options:
- When structure is declared with name only, it MUST not contain
_tsuffix after its name.
struct struct_name {
char* a;
char b;
};
- When structure is declared with typedef only, it has to contain
_tsuffix after its name.
typedef struct {
char* a;
char b;
} struct_name_t;
- When structure is declared with name and typedef, it MUST NOT contain
_tfor basic name and it MUST contain_tsuffix 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
_fnsuffix
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
1nested statement - Every compound statement MUST include single indent; when nesting statements, include
1indent 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
iforif-else-ifstatement,elseMUST 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-whilestatement,whilepart MUST be in the same line as closing bracket ofdopart
// 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
defaultstatement - Add single indent for every
casestatement
// 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/ifndefstatements
- Always document
#ifdef XYZ
/* do something */
#endif /* XYZ */
- Do not indent sub statements inside
#ifstatement
Documentation
- Use doxygen-enabled documentation style for
variables,functionsandstructures/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
briefdescription - Every parameter MUST be noted if it is
inoroutfor input and output respectively - Function MUST include
returnparameter if it returns something. This does not apply forvoidfunctions - Function can include other doxygen keywords, such as
noteorwarning - If function returns member of enumeration, use
refkeyword 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
fileandbriefdescription 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
externfor 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
.cfiles in another.cfile -
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 */