Source: https://github.com/MaJerle/c-code-style
This document describes C code style used by Tilen MAJERLE in his projects and libraries.
Here are listed most obvious and important general rules. Please check them carefully before you continue with other chapters.
- Use
C99standard - 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)- Do not use space between function name and opening bracket
int32_t a = sum(4, 3); /* OK */
int32_t a = sum (4, 3); /* Wrong */- Never use
__or_prefix for variables/functions/macros/types. This is reserved for C language itself- Prefer
prv_name prefix for strictly module-private functions
- Prefer
- Use only lowercase characters for variables/functions/macros/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
staticandglobalvariables to0(orNULL), let compiler do it for you
static int32_t a; /* OK */
static int32_t b = 4; /* OK */
static int32_t a = 0; /* Wrong */
void
my_func(void) {
static int32_t* ptr;/* OK */
static char abc = 0;/* Wrong */
}- Declare all local variables of the same type in the same line
void
my_func(void) {
char a; /* OK */
char a, b; /* OK */
char b; /* Wrong, variable with char type already exists */
}- Declare local variables in order
- Custom structures and enumerations
- Integer types, wider unsigned type first
- Single/Double floating point
int
my_func(void) {
/* 1 */
my_struct_t my; /* First custom structures */
my_struct_ptr_t* p; /* Pointers too */
/* 2 */
uint32_t a;
int32_t b;
uint16_t c;
int16_t g;
char h;
/* ... */
/* 3 */
double d;
float f;
}-
Always declare local variables at the beginning of the block, before first executable statement
-
Declare counter variables in
forloop
/* OK */
for (size_t i = 0; i < 10; ++i)
/* OK, if you need 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) ...- Avoid variable assignment with function call in declaration, except for single variables
void
a(void) {
/* Avoid function calls when declaring variable */
int32_t a, b = sum(1, 2);
/* Use this */
int32_t a, b;
b = sum(1, 2);
/* This is ok */
uint8_t a = 3, b = 4;
}- Except
char,floatordouble, always use types declared instdint.hlibrary, eg.uint8_tforunsigned 8-bit, etc. - Do not use
stdbool.hlibrary. Use1or0fortrueorfalserespectively
/* OK */
uint8_t status;
status = 0;
/* Wrong */
#include <stdbool.h>
bool status = true;- 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 pre-increment (and decrement respectively) instead of post-increment (and decrement respectively)
int32_t a = 0;
...
a++; /* Wrong */
++a; /* OK */
for (size_t j = 0; j < 10; ++j) {} /* OK */- Always use
size_tfor length or size variables - Always use
constfor pointer if function should not modify memory pointed to bypointer - Always use
constfor 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) {
}
/* Not required, it is advised */
void
my_func(const size_t len) {
}
/* When d should not be modified inside function, only data pointed to by d could be modified */
void
my_func(void* const d) {
}- When function may accept pointer of any type, always use
void *, do not useuint8_t *- Function must take care of proper casting in implementation
/*
* To send data, function should not modify memory pointed to by `data` variable
* thus `const` keyword is important
*
* To send generic data (or to write them to file)
* any type may be passed for data,
* thus use `void *`
*/
/* OK example */
void
send_data(const void* data, size_t len) { /* OK */
/* Do not cast `void *` or `const void *` */
const uint8_t* d = data;/* Function handles proper type for internal usage */
}
void
send_data(const void* data, int len) { /* Wrong, not not use int */
}- Always use brackets with
sizeofoperator - Never use Variable Length Array (VLA). Use dynamic memory allocation instead with standard C
mallocandfreefunctions or if library/project provides custom memory allocation, use its implementation- Take a look at LwMEM, custom memory management library
/* OK */
#include <stdlib.h>
void
my_func(size_t size) {
int32_t* arr;
arr = malloc(sizeof(*arr) * n); /* OK, Allocate memory */
arr = malloc(sizeof *arr * n); /* Wrong, brackets for sizeof operator are missing */
if (arr == NULL) {
/* FAIL, no memory */
}
free(arr); /* Free memory after usage */
}
/* Wrong */
void
my_func(size_t size) {
int32_t arr[size]; /* Wrong, do not use VLA */
}- 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 (length > 0) /* OK, length is treated as counter variable containing multi values, not only 0 or 1 */
if (length == 0) /* OK, length is treated as counter variable containing multi values, not only 0 or 1 */
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 */- Always use
/* comment */for comments, even for single-line comment - Always include check for
C++withexternkeyword in header file - 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 - Never cast function returning
void *, eg.uint8_t* ptr = (uint8_t *)func_returning_void_ptr();asvoid *is safely promoted to any other pointer type- Use
uint8_t* ptr = func_returning_void_ptr();instead
- Use
- 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 pointer type, always align asterisk to type, eg.
uint8_t* t = (uint8_t*)var_width_diff_type - Always respect code style already used in project or library
- Comments starting with
//are not allowed. Always use/* comment */, even for single-line comment
//This is comment (wrong)
/* This is comment (ok) */- 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)
*/
/* Single line comment (ok) */- Use
12indents (12 * 4spaces) offset when commenting. If statement is larger than12indents, make comment4-spacesaligned (examples below) to next available indent
void
my_func(void) {
char a, b;
a = call_func_returning_char_a(a); /* This is comment with 12*4 spaces indent from beginning of line */
b = call_func_returning_char_a_but_func_name_is_very_long(a); /* This is comment, aligned to 4-spaces indent */
}- Every function which may have access from outside its module, must include function prototype (or declaration)
- 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 asterisk to 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);- Align all function prototypes (with the same/similar functionality) for better readability
/* OK, function names aligned */
void set(int32_t a);
my_type_t get(void);
my_ptr_t* get_ptr(void);
/* Wrong */
void set(int32_t a);
const char * get(void);- Function implementation must include return type and optional other keywords in separate line
/* OK */
int32_t
foo(void) {
return 0;
}
/* OK */
static const char*
get_string(void) {
return "Hello world!\r\n";
}
/* Wrong */
int32_t foo(void) {
return 0;
}- Make variable name all lowercase with optional underscore
_character
/* OK */
int32_t a;
int32_t my_var;
int32_t myvar;
/* Wrong */
int32_t A;
int32_t myVar;
int32_t MYVar;- Group local variables together by
type
void
foo(void) {
int32_t a, b; /* OK */
char a;
char b; /* Wrong, char type already exists */
}- Do not declare variable after first executable statement
void
foo(void) {
int32_t a;
a = bar();
int32_t b; /* Wrong, there is already executable statement */
}- You may declare new variables inside next indent level
int32_t a, b;
a = foo();
if (a) {
int32_t c, d; /* OK, c and d are in if-statement scope */
c = foo();
int32_t e; /* Wrong, there was already executable statement inside block */
}- Declare pointer variables with asterisk aligned to type
/* OK */
char* a;
/* Wrong */
char *a;
char * a;- When declaring multiple pointer variables, you may declare them with asterisk aligned to variable name
/* OK */
char *p, *n;- 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 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 {
int32_t a;
a = do_a();
do_b(a);
} while (check());
/* Wrong */
do
{
/* ... */
} while (check());
/* Wrong */
do {
/* ... */
}
while (check());- Indentation is required for every opening bracket
if (a) {
do_a();
} else {
do_b();
if (c) {
do_c();
}
}- Never do compound statement without curly bracket, even in case of single statement. Examples below show bad practices
if (a) do_b();
else do_c();
if (a) do_a(); else do_b();- Empty
while,do-whileorforloops must include brackets
/* OK */
while (is_register_bit_set()) {}
/* Wrong */
while (is_register_bit_set());
while (is_register_bit_set()) { }
while (is_register_bit_set()) {
}- If
while(orfor,do-while, etc) is empty (it can be the case in embedded programming), use empty single-line brackets
/* Wait for bit to be set in embedded hardware unit
uint32_t* addr = HW_PERIPH_REGISTER_ADDR;
/* Wait bit 13 to be ready */
while (*addr & (1 << 13)) {} /* OK, empty loop contains no spaces inside curly brackets */
while (*addr & (1 << 13)) { } /* Wrong */
while (*addr & (1 << 13)) { /* Wrong */
}
while (*addr & (1 << 13)); /* Wrong, curly brackets are missing. Can lead to compiler warnings or unintentional bugs */- Always prefer using loops in this order:
for,do-while,while - Avoid incrementing variables inside loop block if possible, see examples
/* Not recommended */
int32_t a = 0;
while (a < 10) {
.
..
...
++a;
}
/* Better */
for (size_t a = 0; a < 10; ++a) {
}
/* Better, if inc may not happen in every cycle */
for (size_t a = 0; a < 10; ) {
if (...) {
++a;
}
}- Add single indent for every
casestatement - Use additional single indent for
breakstatement in eachcaseordefault
/* OK, every case has single indent */
/* OK, every break has additional indent */
switch (check()) {
case 0:
do_a();
break;
case 1:
do_b();
break;
default:
break;
}
/* Wrong, case indent missing */
switch (check()) {
case 0:
do_a();
break;
case 1:
do_b();
break;
default:
break;
}
/* Wrong */
switch (check()) {
case 0:
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;
}- Always include
defaultstatement
/* OK */
switch (var) {
case 0:
do_job();
break;
default:
break;
}
/* Wrong, default is missing */
switch (var) {
case 0:
do_job();
break;
}- If local variables are required, use curly brackets and put
breakstatement inside.- Put opening curly bracket in the same line as
casestatement
- Put opening curly bracket in the same line as
switch (a) {
/* OK */
case 0: {
int32_t a, b;
char c;
a = 5;
/* ... */
break;
}
/* Wrong */
case 1:
{
int32_t a;
break;
}
/* Wrong, break shall be inside */
case 2: {
int32_t a;
}
break;
}