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
C99
standard - 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)
- 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
static
andglobal
variables 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
for
loop
/* 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
,float
ordouble
, always use types declared instdint.h
library, eg.uint8_t
forunsigned 8-bit
, etc. - Do not use
stdbool.h
library. Use1
or0
fortrue
orfalse
respectively
/* 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
NULL
value
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_t
for length or size variables - Always use
const
for pointer if function should not modify memory pointed to bypointer
- Always use
const
for 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
sizeof
operator - Never use Variable Length Array (VLA). Use dynamic memory allocation instead with standard C
malloc
andfree
functions 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
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 (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++
withextern
keyword 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+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)
*/
/* Single line comment (ok) */
- Use
12
indents (12 * 4
spaces) offset when commenting. If statement is larger than12
indents, make comment4-spaces
aligned (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
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
orif-else-if
statement,else
must 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-while
statement,while
part must be in the same line as closing bracket ofdo
part
/* 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-while
orfor
loops 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
case
statement - Use additional single indent for
break
statement in eachcase
ordefault
/* 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
default
statement
/* 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
break
statement inside.- Put opening curly bracket in the same line as
case
statement
- 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;
}