在逛GitHub时,发现一篇嵌入式C编码规范,写的比较详细。在学习的同时,给翻译了下加深学习。

image-20220904161239766

本文翻译自:Recommended C style and coding rules

推荐的 C 风格和编码规则

本文描述了 Tilen MAJERLE 在他的项目和库中使用的 C 代码风格。

最重要的一条规则

让我们从 GNOME developer 站点的引用开始。

编写代码时最重要的一条规则是:检查周围的代码并尝试模仿它

作为一个维护者,如果收到一个明显与周围代码风格不同的补丁,这是是令人沮丧的。这是不尊重人的,就像有人穿着泥泞的鞋子走进一间一尘不染的房子一样。

因此,无论本文档推荐什么,如果已经编写了代码,并且你正在修补它,请保持它当前的样式一致,即使它不是你最喜欢的样式。

一般规则

这里列出了最明显和最重要的一般规则。在你继续阅读其他章节之前,请仔细检查它们。

  • 使用 C99 标准
  • 不使用制表符,使用空格代替
  • 每个缩进级别使用 4 空格
  • 在关键字和左括号之间使用 1 空格
1
2
3
4
5
6
7
8
9
10
11
/* OK */
if (condition)
while (condition)
for (init; condition; step)
do {} while (condition)

/* Wrong */
if(condition)
while(condition)
for(init;condition;step)
do {} while(condition)
  • 在函数名和左括号之间不要使用空格
1
2
int32_t a = sum(4, 3);              /* OK */
int32_t a = sum (4, 3); /* Wrong */
  • 不要为变量/函数/宏/类型使用 ___ 前缀。这是为 C 语言本身保留的
    • 对于严格的模块私有函数,首选 prv_ 名称前缀
  • 对于包含下划线 _ char 的变量/函数/宏/类型,只能使用小写字母
  • 左花括号总是与关键字在同一行 ( forwhiledoswitchif,…)
1
2
3
4
5
6
7
8
size_t i;
for (i = 0; i < 5; ++i) { /* OK */
}
for (i = 0; i < 5; ++i){ /* Wrong */
}
for (i = 0; i < 5; ++i) /* Wrong */
{
}
  • 在比较操作符和赋值操作符前后使用单个空格
1
2
3
4
5
6
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 */
  • 在每个逗号后使用一个空格
1
2
func_name(5, 4);        /* OK */
func_name(4,3); /* Wrong */
  • 不要初始化 staticglobal 变量到 0 (或 NULL),让编译器为你做
1
2
3
4
5
6
7
8
9
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 */
}
  • 在同一行声明相同类型的所有局部变量
1
2
3
4
5
6
void
my_func(void) {
char a; /* OK */
char b; /* Wrong, variable with char type already exists */
char a, b; /* OK */
}
  • 按顺序声明局部变量
    1. 自定义结构和枚举
    2. 整型,更宽的无符号类型优先
    3. 单/双浮点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}
  • 总是在块的开头,在第一个可执行语句之前声明局部变量

  • for 循环中声明计数器变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 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) ...
  • 避免在声明中使用函数调用对变量赋值,除了单个变量
1
2
3
4
5
6
7
8
9
10
11
12
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;
}
  • 除了 charfloatdouble,总是使用在 stdint.h 库中声明的类型,例如。uint8_t 表示unsigned 8-bit等。
  • 不要使用 stdbool.h 库。分别使用 1 或者 0 表示 or
1
2
3
4
5
6
7
/* OK */
uint8_t status;
status = 0;

/* Wrong */
#include <stdbool.h>
bool status = true;
  • 不要与 true 比较,例如。if (check_func() == 1),使用 if (check_func()) {...}
  • 总是与 NULL 值比较指针
1
2
3
4
5
6
7
8
9
10
11
12
13
void* ptr;

/* ... */

/* OK, compare against NULL */
if (ptr == NULL || ptr != NULL) {

}

/* Wrong */
if (ptr || !ptr) {

}
  • 总是使用 前增量 (和递减) 而不是 后增量 (和递减)
1
2
3
4
5
6
7
int32_t a = 0;
...

a++; /* Wrong */
++a; /* OK */

for (size_t j = 0; j < 10; ++j) {} /* OK */
  • 长度或大小变量总是使用 size_t
  • 如果函数不应该修改 指针 指向的内存,则始终使用 const 作为指针
  • 如果不应该修改函数参数或变量,请始终使用 const
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 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) {

}
  • 当函数可以接受任何类型的指针时,始终使用 void *,不要使用 uint8_t *
    • 函数在实现时必须注意正确的类型转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* 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 */
}
  • 始终使用括号与 sizeof 操作符
  • 不要使用 变长数组 (VLA)。使用动态内存分配代替标准的 C mallocfree 函数,或者如果库/项目提供自定义内存分配,使用它的实现
    • 看看 LwMEM,自定义内存管理库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 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 */
}
  • 始终将变量与 0 比较,除非它被处理为 布尔 类型
  • 不要将 布尔处理的 变量与 0 或 1 进行比较。使用 NOT ( !) 代替
1
2
3
4
5
6
7
8
9
10
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 */
  • 注释总是使用 /* comment */,即使是 单行 注释
  • 总是在头文件中包含带 extern 关键字的 C++ 检查
  • 每个函数必须包含 doxygen-enabled 注释,即使函数是 static
  • 函数、变量、注释使用英文名称/文本
  • 变量使用 小写 字母
  • 如果变量包含多个名称,使用 下划线。 例如,使用 force_redraw,不要使用 forceRedraw
  • 永远不要强制转换返回 void * 的函数,例如,uint8_t* ptr = (uint8_t *)func_returning_void_ptr(); 作为 void * 被安全提升为任何其他指针类型
    • 使用 uint8_t* ptr = func_returning_void_ptr(); 来代替
  • 对于 C 标准库包含的文件,通常使用 <>,例如, #include <stdlib.h>
  • 对于自定义库,总是使用 "",例如, #include "my_library.h"
  • 当转换为指针类型时,总是将星号对齐到类型,例如,uint8_t* t = (uint8_t*)var_width_diff_type
  • 始终尊重项目或库中已经使用的代码风格

注释

  • 不允许以 // 开头的注释。始终使用 /* comment */,即使是单行注释
1
2
//This is comment (wrong)
/* This is comment (ok) */
  • 对于多行注释,每行使用 空格+星号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* 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) */
  • 注释时使用 12 个缩进 ( 12 * 4 空格) 偏移量。如果语句大于 12 缩进,将注释 4-空格 对齐 (下面的例子) 到下一个可用的缩进
1
2
3
4
5
6
7
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 */
}

函数

  • 每个可以从其模块外部访问的函数必须包含 函数原型(或 声明)
  • 函数名必须是小写的,可以用下划线分隔 _ 字符
1
2
3
4
5
6
7
/* OK */
void my_func(void);
void myfunc(void);

/* Wrong */
void MYFunc(void);
void myFunc();
  • 当函数返回指针时,将星号对齐到返回类型
1
2
3
4
5
6
7
/* 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);
  • 对齐所有函数原型 (具有相同/相似的功能) 以获得更好的可读性
1
2
3
4
5
6
7
8
/* 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);
  • 函数实现必须在单独的行中包含返回类型和可选的其他关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 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;
}

变量

  • 使变量名全部小写,可选下划线 _ 字符
1
2
3
4
5
6
7
8
9
/* OK */
int32_t a;
int32_t my_var;
int32_t myvar;

/* Wrong */
int32_t A;
int32_t myVar;
int32_t MYVar;
  • 根据类型 将局部变量分组在一起
1
2
3
4
5
6
void
foo(void) {
int32_t a, b; /* OK */
char a;
char b; /* Wrong, char type already exists */
}
  • 不要在第一个可执行语句后声明变量
1
2
3
4
5
6
void
foo(void) {
int32_t a;
a = bar();
int32_t b; /* Wrong, there is already executable statement */
}
  • 你可以在下一个缩进层声明新的变量
1
2
3
4
5
6
7
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 */
}
  • 用星号声明指针变量与类型对齐
1
2
3
4
5
6
/* OK */
char* a;

/* Wrong */
char *a;
char * a;
  • 当声明多个指针变量时,可以用星号对变量名进行声明
1
2
/* OK */
char *p, *n;

结构、枚举类型定义

  • 结构或枚举名称必须是小写的,单词之间可用下划线 _
  • 结构或枚举可能包含 typedef 关键字
  • 所有结构成员必须是小写的
  • 所有枚举成员必须为大写
  • 结构/枚举必须遵循 doxygen 文档语法

当声明结构体时,它可以使用以下 3 不同的选项之一:

  1. 当结构体只用name 声明时,名称后绝对不能包含 _t 后缀。
1
2
3
4
struct struct_name {
char* a;
char b;
};
  1. 当结构只使用 typedef 声明时,名称后必须包含 _t 后缀。
1
2
3
4
typedef struct {
char* a;
char b;
} struct_name_t;
  1. 当结构体使用 name 和 typedef 声明时,它不能包含 _t 作为基本名称,必须在它的名称后面包含_t后缀作为typedef部分
1
2
3
4
5
typedef struct struct_name {
char* a;
char b;
char c;
} struct_name_t;

错误声明的例子及建议的纠正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* a and b must be separated to 2 lines */
/* Name of structure with typedef must include _t suffix */
typedef struct {
int32_t a, b;
} a;

/* Corrected version */
typedef struct {
int32_t a;
int32_t b;
} a_t;

/* Wrong name, it must not include _t suffix */
struct name_t {
int32_t a;
int32_t b;
};

/* Wrong parameters, must be all uppercase */
typedef enum {
MY_ENUM_TESTA,
my_enum_testb,
} my_enum_t;
  • 当在声明中初始化结构时,使用 C99 初始化样式
1
2
3
4
5
6
7
8
/* OK */
a_t a = {
.a = 4,
.b = 5,
};

/* Wrong */
a_t a = {1, 2};
  • 当为函数句柄引入新的 typedef 时,使用 _fn 后缀
1
2
3
/* Function accepts 2 parameters and returns uint8_t */
/* Name of typedef has `_fn` suffix */
typedef uint8_t (*my_func_typedef_fn)(uint8_t p1, const char* p2);

复合语句

  • 每个复合语句必须包含左花括号和右花括号,即使它只包含 1 个嵌套语句
  • 每个复合语句必须包含单个缩进;当嵌套语句时,为每个嵌套包含 1 个缩进大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* OK */
if (c) {
do_a();
} else {
do_b();
}

/* Wrong */
if (c)
do_a();
else
do_b();

/* Wrong */
if (c) do_a();
else do_b();
  • 对于 ifif-else-if 语句,else 必须与第一个语句的右括号在同一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* OK */
if (a) {

} else if (b) {

} else {

}

/* Wrong */
if (a) {

}
else {

}

/* Wrong */
if (a) {

}
else
{

}
  • 对于 do-while 语句,while 部分必须与 do 部分的右括号在同一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* OK */
do {
int32_t a;
a = do_a();
do_b(a);
} while (check());

/* Wrong */
do
{
/* ... */
} while (check());

/* Wrong */
do {
/* ... */
}
while (check());
  • 每一个开括号都要缩进
1
2
3
4
5
6
7
8
if (a) {
do_a();
} else {
do_b();
if (c) {
do_c();
}
}
  • 不要做没有花括号的复合语句,即使是单个语句。下面的例子展示了一些不好的做法
1
2
3
4
if (a) do_b();
else do_c();

if (a) do_a(); else do_b();
  • whiledo-whilefor 循环必须包含括号
1
2
3
4
5
6
7
8
/* OK */
while (is_register_bit_set()) {}

/* Wrong */
while (is_register_bit_set());
while (is_register_bit_set()) { }
while (is_register_bit_set()) {
}
  • 如果 while (或 fordo-while,等等) 为空 (嵌入式编程中也有这种情况),使用空的单行括号
1
2
3
4
5
6
7
8
9
10
/* 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 */
  • 总是倾向于这样顺序使用循环: fordo-whilewhile
  • 尽量避免在循环块内递增变量,参见示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 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;
}
}

分支语句

  • 为每个 case 语句添加 单个缩进
  • 对于 casedefault 中的 break 语句,使用附加的 单个缩进
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* 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;
}
  • 总是包含 default 语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* OK */
switch (var) {
case 0:
do_job();
break;
default:
break;
}

/* Wrong, default is missing */
switch (var) {
case 0:
do_job();
break;
}
  • 如果需要局部变量,使用大括号并将 break 语句放入其中。
    • 把左大括号放在与 case 语句同一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}

宏和预处理指令

  • 总是使用宏而不是文字常量,特别是对于数字
  • 所有宏必须完全大写,可选下划线 _ 字符,除非它们被明确地标记为函数,将来可能会被常规函数语法替换
1
2
3
4
5
/* OK */
#define MY_MACRO(x) ((x) * (x))

/* Wrong */
#define square(x) ((x) * (x))
  • 总是用圆括号保护输入参数
1
2
3
4
5
/* OK */
#define MIN(x, y) ((x) < (y) ? (x) : (y))

/* Wrong */
#define MIN(x, y) x < y ? x : y
  • 总是用括号来保护最终的宏计算
1
2
3
4
5
6
7
8
9
10
11
/* Wrong */
#define MIN(x, y) (x) < (y) ? (x) : (y)
#define SUM(x, y) (x) + (y)

/* Imagine result of this equation using wrong SUM implementation */
int32_t x = 5 * SUM(3, 4); /* Expected result is 5 * 7 = 35 */
int32_t x = 5 * (3) + (4); /* It is evaluated to this, final result = 19 which is not what we expect */

/* Correct implementation */
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define SUM(x, y) ((x) + (y))
  • 当宏使用多个语句时,使用 do-while (0) 语句保护它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
typedef struct {
int32_t px, py;
} point_t;
point_t p; /* Define new point */

/* Wrong implementation */

/* Define macro to set point */
#define SET_POINT(p, x, y) (p)->px = (x); (p)->py = (y) /* 2 statements. Last one should not implement semicolon */

SET_POINT(&p, 3, 4); /* Set point to position 3, 4. This evaluates to... */
(&p)->px = (3); (&p)->py = (4); /* ... to this. In this example this is not a problem. */

/* Consider this ugly code, however it is valid by C standard (not recommended) */
if (a) /* If a is true */
if (b) /* If b is true */
SET_POINT(&p, 3, 4);/* Set point to x = 3, y = 4 */
else
SET_POINT(&p, 5, 6);/* Set point to x = 5, y = 6 */

/* Evaluates to code below. Do you see the problem? */
if (a)
if (b)
(&p)->px = (3); (&p)->py = (4);
else
(&p)->px = (5); (&p)->py = (6);

/* Or if we rewrite it a little */
if (a)
if (b)
(&p)->px = (3);
(&p)->py = (4);
else
(&p)->px = (5);
(&p)->py = (6);

/*
* Ask yourself a question: To which `if` statement `else` keyword belongs?
*
* Based on first part of code, answer is straight-forward. To inner `if` statement when we check `b` condition
* Actual answer: Compilation error as `else` belongs nowhere
*/

/* Better and correct implementation of macro */
#define SET_POINT(p, x, y) do { (p)->px = (x); (p)->py = (y); } while (0) /* 2 statements. No semicolon after while loop */
/* Or even better */
#define SET_POINT(p, x, y) do { \ /* Backslash indicates statement continues in new line */
(p)->px = (x); \
(p)->py = (y); \
} while (0) /* 2 statements. No semicolon after while loop */

/* Now original code evaluates to */
if (a)
if (b)
do { (&p)->px = (3); (&p)->py = (4); } while (0);
else
do { (&p)->px = (5); (&p)->py = (6); } while (0);

/* Every part of `if` or `else` contains only `1` inner statement (do-while), hence this is valid evaluation */

/* To make code perfect, use brackets for every if-ifelse-else statements */
if (a) { /* If a is true */
if (b) { /* If b is true */
SET_POINT(&p, 3, 4);/* Set point to x = 3, y = 4 */
} else {
SET_POINT(&p, 5, 6);/* Set point to x = 5, y = 6 */
}
}
  • 避免使用 #ifdef#ifndef。使用 defined()!defined() 来代替
1
2
3
#ifdef XYZ
/* do something */
#endif /* XYZ */
  • 总是记录 if/elif/else/endif 语句
1
2
3
4
5
6
7
8
9
10
11
12
13
/* OK */
#if defined(XYZ)
/* Do if XYZ defined */
#else /* defined(XYZ) */
/* Do if XYZ not defined */
#endif /* !defined(XYZ) */

/* Wrong */
#if defined(XYZ)
/* Do if XYZ defined */
#else
/* Do if XYZ not defined */
#endif
  • 不缩进子语句里面 #if 语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* OK */
#if defined(XYZ)
#if defined(ABC)
/* do when ABC defined */
#endif /* defined(ABC) */
#else /* defined(XYZ) */
/* Do when XYZ not defined */
#endif /* !defined(XYZ) */

/* Wrong */
#if defined(XYZ)
#if defined(ABC)
/* do when ABC defined */
#endif /* defined(ABC) */
#else /* defined(XYZ) */
/* Do when XYZ not defined */
#endif /* !defined(XYZ) */

文档

文档化的代码允许 doxygen 解析和通用 html/pdf/latex 输出,因此正确地做这件事是非常重要的。

  • 对于 变量函数结构体/枚举,使用doxygen支持的文档样式
  • doxygen总是使用 \,不要使用 @
  • 对于文本,始终使用 5x4 空格 ( 5 个制表符) 从行首偏移
1
2
3
4
5
6
/**
* \brief Holds pointer to first entry in linked list
* Beginning of this text is 5 tabs (20 spaces) from beginning of line
*/
static
type_t* list;
  • 每个结构/枚举成员都必须包含文档
  • 注释的开头使用 12x4 空格 偏移量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* \brief This is point struct
* \note This structure is used to calculate all point
* related stuff
*/
typedef struct {
int32_t x; /*!< Point X coordinate */
int32_t y; /*!< Point Y coordinate */
int32_t size; /*!< Point size.
Since comment is very big,
you may go to next line */
} point_t;

/**
* \brief Point color enumeration
*/
typedef enum {
COLOR_RED, /*!< Red color. This comment has 12x4
spaces offset from beginning of line */
COLOR_GREEN, /*!< Green color */
COLOR_BLUE, /*!< Blue color */
} point_color_t;
  • 函数的文档必须在函数实现中编写 (通常是源文件)
  • 函数必须包含 简介 和所有参数文档
  • 必须注意每个参数是否分别用于输入输出
  • 如果函数返回值,则必须包含 return 参数。这不适用于 void 函数
  • 函数可以包含其他 doxygen 关键字,如 notewarning
  • 在参数名和描述之间使用冒号 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* \brief Sum `2` numbers
* \param[in] a: First number
* \param[in] b: Second number
* \return Sum of input values
*/
int32_t
sum(int32_t a, int32_t b) {
return a + b;
}

/**
* \brief Sum `2` numbers and write it to pointer
* \note This function does not return value, it stores it to pointer instead
* \param[in] a: First number
* \param[in] b: Second number
* \param[out] result: Output variable used to save result
*/
void
void_sum(int32_t a, int32_t b, int32_t* result) {
*result = a + b;
}
  • 如果函数返回枚举成员,使用 ref 关键字指定哪一个成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* \brief My enumeration
*/
typedef enum {
MY_ERR, /*!< Error value */
MY_OK /*!< OK value */
} my_enum_t;

/**
* \brief Check some value
* \return \ref MY_OK on success, member of \ref my_enum_t otherwise
*/
my_enum_t
check_value(void) {
return MY_OK;
}
  • 对常数或数字使用符号 (\ NULL\ => NULL)
1
2
3
4
5
6
7
8
9
/**
* \brief Get data from input array
* \param[in] in: Input data
* \return Pointer to output data on success, `NULL` otherwise
*/
const void *
get_data(const void* in) {
return in;
}
  • 宏的文档必须包括 hideinitializer doxygen 命令
1
2
3
4
5
6
7
8
/**
* \brief Get minimal value between `x` and `y`
* \param[in] x: First value
* \param[in] y: Second value
* \return Minimal value between `x` and `y`
* \hideinitializer
*/
#define MIN(x, y) ((x) < (y) ? (x) : (y))

头/源文件

  • 在文件末尾留下一个空行
  • 每个文件都必须包括文件的doxygen注释和后跟空行的简要描述(使用doxygen时)
1
2
3
4
5
/**
* \file template.h
* \brief Template include file
*/
/* Here is empty line */
  • 每个文件 (头文件源文件) 必须包含许可证 (开头注释包含一个星号,因为doxygen必须忽略它)
  • 使用与项目/库已经使用的相同的许可证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* \file template.h
* \brief Template include file
*/

/*
* Copyright (c) year FirstName LASTNAME
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* This file is part of library_name.
*
* Author: FirstName LASTNAME <optional_email@example.com>
*/
  • 头文件必须包含保护符 #ifndef
  • 头文件必须包含 C++ 检查
  • C++ 检查之外包含外部头文件
  • 包括外部头文件,首先是 STL C 文件,然后是应用程序自定义文件
  • 为了正确编译,头文件必须只包含其他所有头文件,但不能包含更多 (.c 应在需要时包含其他头文件)
  • 头文件必须只公开模块公共变量/类型/函数
  • 在头文件中使用 extern 作为全局模块变量,稍后在源文件中定义它们
1
2
3
4
5
6
7
8
9
/* 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 */
  • 不要将 .c 文件包含在另一个 .c 文件中

  • .c 文件应该首先包含对应的 .h 文件,之后包含其他文件,除非另有明确需要

  • 在头文件中不包括模块私有声明

  • 头文件示例 (示例中没有许可证)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* License comes here */
#ifndef TEMPLATE_HDR_H
#define TEMPLATE_HDR_H

/* Include headers */

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

/* File content here */

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* TEMPLATE_HDR_H */

Artistic style配置

AStyle 是一个很棒的软件,它可以帮助根据输入配置格式化代码。

此存储库包含 astyle-code-format.cfg 文件,可与 AStyle 软件一起使用。

1
astyle --options="astyle-code-format.cfg" "input_path/*.c,*.h" "input_path2/*.c,*.h"

Eclipse 格式化

存储库包含 eclipse-ext-kr-format.xml 文件,可以与基于 eclipse 的工具链一起使用,以设置格式化器选项。

它基于 K&R 格式器,并根据上述规则进行了修改。你可以在 eclipse 设置中导入它,Preferences -> LANGUAGE -> Code Style -> Formatter 选项卡。

This document describes C code style used by Tilen MAJERLE in his projects and libraries.

The single most important rule

Let’s start with the quote from GNOME developer site.

The single most important rule when writing code is this: check the surrounding code and try to imitate it.

As a maintainer it is dismaying to receive a patch that is obviously in a different coding style to the surrounding code. This is disrespectful, like someone tromping into a spotlessly-clean house with muddy shoes.

So, whatever this document recommends, if there is already written code and you are patching it, keep its current style consistent even if it is not your favorite style.

General rules

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
1
2
3
4
5
6
7
8
9
10
11
/* 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
1
2
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
  • 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, …)
1
2
3
4
5
6
7
8
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
1
2
3
4
5
6
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
1
2
func_name(5, 4);        /* OK */
func_name(4,3); /* Wrong */
  • Do not initialize static and global variables to 0 (or NULL), let compiler do it for you
1
2
3
4
5
6
7
8
9
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
1
2
3
4
5
6
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
    1. Custom structures and enumerations
    2. Integer types, wider unsigned type first
    3. Single/Double floating point
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 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
1
2
3
4
5
6
7
8
9
10
11
12
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 or double, always use types declared in stdint.h library, eg. uint8_t for unsigned 8-bit, etc.
  • Do not use stdbool.h library. Use 1 or 0 for true or false respectively
1
2
3
4
5
6
7
/* OK */
uint8_t status;
status = 0;

/* Wrong */
#include <stdbool.h>
bool status = true;
  • Never compare against true, eg. if (check_func() == 1), use if (check_func()) { ... }
  • Always compare pointers against NULL value
1
2
3
4
5
6
7
8
9
10
11
12
13
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)
1
2
3
4
5
6
7
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 by pointer
  • Always use const for function parameter or variable, if it should not be modified
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 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 use uint8_t *
    • Function must take care of proper casting in implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* 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 and free functions or if library/project provides custom memory allocation, use its implementation
    • Take a look at LwMEM, custom memory management library
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 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
1
2
3
4
5
6
7
8
9
10
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++ with extern 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 use forceRedraw
  • Never cast function returning void *, eg. uint8_t* ptr = (uint8_t *)func_returning_void_ptr(); as void * is safely promoted to any other pointer type
    • Use uint8_t* ptr = func_returning_void_ptr(); instead
  • 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

  • Comments starting with // are not allowed. Always use /* comment */, even for single-line comment
1
2
//This is comment (wrong)
/* This is comment (ok) */
  • For multi-line comments use space+asterisk for every line
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* 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 than 12 indents, make comment 4-spaces aligned (examples below) to next available indent
1
2
3
4
5
6
7
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 */
}

Functions

  • 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
1
2
3
4
5
6
7
/* OK */
void my_func(void);
void myfunc(void);

/* Wrong */
void MYFunc(void);
void myFunc();
  • When function returns pointer, align asterisk to return type
1
2
3
4
5
6
7
/* 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
1
2
3
4
5
6
7
8
/* 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 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;
}

Variables

  • Make variable name all lowercase with optional underscore _ character
1
2
3
4
5
6
7
8
9
/* 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
1
2
3
4
5
6
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
1
2
3
4
5
6
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
1
2
3
4
5
6
7
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
1
2
3
4
5
6
/* OK */
char* a;

/* Wrong */
char *a;
char * a;
  • When declaring multiple pointer variables, you may declare them with asterisk aligned to variable name
1
2
/* OK */
char *p, *n;

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 must be lowercase
  • All enumeration members must be uppercase
  • Structure/enumeration must follow doxygen documentation syntax

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.
1
2
3
4
struct struct_name {
char* a;
char b;
};
  1. When structure is declared with typedef only, it has to contain _t suffix after its name.
1
2
3
4
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 has to contain _t suffix after its name for typedef part.
1
2
3
4
5
typedef struct struct_name {
char* a;
char b;
char c;
} struct_name_t;

Examples of bad declarations and their suggested corrections

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* a and b must be separated to 2 lines */
/* Name of structure with typedef must include _t suffix */
typedef struct {
int32_t a, b;
} a;

/* Corrected version */
typedef struct {
int32_t a;
int32_t b;
} a_t;

/* Wrong name, it must not include _t suffix */
struct name_t {
int32_t a;
int32_t b;
};

/* Wrong parameters, must be all uppercase */
typedef enum {
MY_ENUM_TESTA,
my_enum_testb,
} my_enum_t;
  • When initializing structure on declaration, use C99 initialization style
1
2
3
4
5
6
7
8
/* OK */
a_t a = {
.a = 4,
.b = 5,
};

/* Wrong */
a_t a = {1, 2};
  • When new typedef is introduced for function handles, use _fn suffix
1
2
3
/* Function accepts 2 parameters and returns uint8_t */
/* Name of typedef has `_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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 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 first statement
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 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
1
2
3
4
5
6
7
8
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
1
2
3
4
if (a) do_b();
else do_c();

if (a) do_a(); else do_b();
  • Empty while, do-while or for loops must include brackets
1
2
3
4
5
6
7
8
/* OK */
while (is_register_bit_set()) {}

/* Wrong */
while (is_register_bit_set());
while (is_register_bit_set()) { }
while (is_register_bit_set()) {
}
  • If while (or for, do-while, etc) is empty (it can be the case in embedded programming), use empty single-line brackets
1
2
3
4
5
6
7
8
9
10
/* 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 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;
}
}

Switch statement

  • Add single indent for every case statement
  • Use additional single indent for break statement in each case or default
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}

Macros and preprocessor directives

  • Always use macros instead of literal constants, specially for numbers
  • All macros must be fully uppercase, with optional underscore _ character, except if they are clearly marked as function which may be in the future replaced with regular function syntax
1
2
3
4
5
/* OK */
#define MY_MACRO(x) ((x) * (x))

/* Wrong */
#define square(x) ((x) * (x))
  • Always protect input parameters with parentheses
1
2
3
4
5
/* OK */
#define MIN(x, y) ((x) < (y) ? (x) : (y))

/* Wrong */
#define MIN(x, y) x < y ? x : y
  • Always protect final macro evaluation with parenthesis
1
2
3
4
5
6
7
8
9
10
11
/* Wrong */
#define MIN(x, y) (x) < (y) ? (x) : (y)
#define SUM(x, y) (x) + (y)

/* Imagine result of this equation using wrong SUM implementation */
int32_t x = 5 * SUM(3, 4); /* Expected result is 5 * 7 = 35 */
int32_t x = 5 * (3) + (4); /* It is evaluated to this, final result = 19 which is not what we expect */

/* Correct implementation */
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define SUM(x, y) ((x) + (y))
  • When macro uses multiple statements, protect it using do-while (0) statement
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
typedef struct {
int32_t px, py;
} point_t;
point_t p; /* Define new point */

/* Wrong implementation */

/* Define macro to set point */
#define SET_POINT(p, x, y) (p)->px = (x); (p)->py = (y) /* 2 statements. Last one should not implement semicolon */

SET_POINT(&p, 3, 4); /* Set point to position 3, 4. This evaluates to... */
(&p)->px = (3); (&p)->py = (4); /* ... to this. In this example this is not a problem. */

/* Consider this ugly code, however it is valid by C standard (not recommended) */
if (a) /* If a is true */
if (b) /* If b is true */
SET_POINT(&p, 3, 4);/* Set point to x = 3, y = 4 */
else
SET_POINT(&p, 5, 6);/* Set point to x = 5, y = 6 */

/* Evaluates to code below. Do you see the problem? */
if (a)
if (b)
(&p)->px = (3); (&p)->py = (4);
else
(&p)->px = (5); (&p)->py = (6);

/* Or if we rewrite it a little */
if (a)
if (b)
(&p)->px = (3);
(&p)->py = (4);
else
(&p)->px = (5);
(&p)->py = (6);

/*
* Ask yourself a question: To which `if` statement `else` keyword belongs?
*
* Based on first part of code, answer is straight-forward. To inner `if` statement when we check `b` condition
* Actual answer: Compilation error as `else` belongs nowhere
*/

/* Better and correct implementation of macro */
#define SET_POINT(p, x, y) do { (p)->px = (x); (p)->py = (y); } while (0) /* 2 statements. No semicolon after while loop */
/* Or even better */
#define SET_POINT(p, x, y) do { \ /* Backslash indicates statement continues in new line */
(p)->px = (x); \
(p)->py = (y); \
} while (0) /* 2 statements. No semicolon after while loop */

/* Now original code evaluates to */
if (a)
if (b)
do { (&p)->px = (3); (&p)->py = (4); } while (0);
else
do { (&p)->px = (5); (&p)->py = (6); } while (0);

/* Every part of `if` or `else` contains only `1` inner statement (do-while), hence this is valid evaluation */

/* To make code perfect, use brackets for every if-ifelse-else statements */
if (a) { /* If a is true */
if (b) { /* If b is true */
SET_POINT(&p, 3, 4);/* Set point to x = 3, y = 4 */
} else {
SET_POINT(&p, 5, 6);/* Set point to x = 5, y = 6 */
}
}
  • Avoid using #ifdef or #ifndef. Use defined() or !defined() instead
1
2
3
#ifdef XYZ
/* do something */
#endif /* XYZ */
  • Always document if/elif/else/endif statements
1
2
3
4
5
6
7
8
9
10
11
12
13
/* OK */
#if defined(XYZ)
/* Do if XYZ defined */
#else /* defined(XYZ) */
/* Do if XYZ not defined */
#endif /* !defined(XYZ) */

/* Wrong */
#if defined(XYZ)
/* Do if XYZ defined */
#else
/* Do if XYZ not defined */
#endif
  • Do not indent sub statements inside #if statement
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* OK */
#if defined(XYZ)
#if defined(ABC)
/* do when ABC defined */
#endif /* defined(ABC) */
#else /* defined(XYZ) */
/* Do when XYZ not defined */
#endif /* !defined(XYZ) */

/* Wrong */
#if defined(XYZ)
#if defined(ABC)
/* do when ABC defined */
#endif /* defined(ABC) */
#else /* defined(XYZ) */
/* Do when XYZ not defined */
#endif /* !defined(XYZ) */

Documentation

Documented code allows doxygen to parse and general html/pdf/latex output, thus it is very important to do it properly.

  • Use doxygen-enabled documentation style for variables, functions and structures/enumerations
  • Always use \ for doxygen, do not use @
  • Always use 5x4 spaces (5 tabs) offset from beginning of line for text
1
2
3
4
5
6
/**
* \brief Holds pointer to first entry in linked list
* Beginning of this text is 5 tabs (20 spaces) from beginning of line
*/
static
type_t* list;
  • Every structure/enumeration member must include documentation
  • Use 12x4 spaces offset for beginning of comment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* \brief This is point struct
* \note This structure is used to calculate all point
* related stuff
*/
typedef struct {
int32_t x; /*!< Point X coordinate */
int32_t y; /*!< Point Y coordinate */
int32_t size; /*!< Point size.
Since comment is very big,
you may go to next line */
} point_t;

/**
* \brief Point color enumeration
*/
typedef enum {
COLOR_RED, /*!< Red color. This comment has 12x4
spaces offset from beginning of line */
COLOR_GREEN, /*!< Green color */
COLOR_BLUE, /*!< Blue color */
} point_color_t;
  • Documentation for functions must be written in function implementation (source file usually)
  • Function must include brief and all parameters documentation
  • 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
  • Use colon : between parameter name and its description
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* \brief Sum `2` numbers
* \param[in] a: First number
* \param[in] b: Second number
* \return Sum of input values
*/
int32_t
sum(int32_t a, int32_t b) {
return a + b;
}

/**
* \brief Sum `2` numbers and write it to pointer
* \note This function does not return value, it stores it to pointer instead
* \param[in] a: First number
* \param[in] b: Second number
* \param[out] result: Output variable used to save result
*/
void
void_sum(int32_t a, int32_t b, int32_t* result) {
*result = a + b;
}
  • If function returns member of enumeration, use ref keyword to specify which one
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* \brief My enumeration
*/
typedef enum {
MY_ERR, /*!< Error value */
MY_OK /*!< OK value */
} my_enum_t;

/**
* \brief Check some value
* \return \ref MY_OK on success, member of \ref my_enum_t otherwise
*/
my_enum_t
check_value(void) {
return MY_OK;
}
  • Use notation (`NULL` => NULL) for constants or numbers
1
2
3
4
5
6
7
8
9
/**
* \brief Get data from input array
* \param[in] in: Input data
* \return Pointer to output data on success, `NULL` otherwise
*/
const void *
get_data(const void* in) {
return in;
}
  • Documentation for macros must include hideinitializer doxygen command
1
2
3
4
5
6
7
8
/**
* \brief Get minimal value between `x` and `y`
* \param[in] x: First value
* \param[in] y: Second value
* \return Minimal value between `x` and `y`
* \hideinitializer
*/
#define MIN(x, y) ((x) < (y) ? (x) : (y))

Header/source files

  • Leave single empty line at the end of file
  • Every file must include doxygen annotation for file and brief description followed by empty line (when using doxygen)
1
2
3
4
5
/**
* \file template.h
* \brief Template include file
*/
/* Here is empty line */
  • Every file (header or source) must include license (opening comment includes single asterisk as this must be ignored by doxygen)
  • Use the same license as already used by project/library
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* \file template.h
* \brief Template include file
*/

/*
* Copyright (c) year FirstName LASTNAME
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* This file is part of library_name.
*
* Author: FirstName LASTNAME <optional_email@example.com>
*/
  • Header file must include guard #ifndef
  • Header file must include C++ check
  • Include external header files outside C++ check
  • Include external header files with STL C files first followed by application custom files
  • Header file must include only every other header file in order 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
1
2
3
4
5
6
7
8
9
/* 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

  • .c file should first include corresponding .h file, later others, unless otherwise explicitly necessary

  • Do not include module private declarations in header file

  • Header file example (no license for sake of an example)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* License comes here */
#ifndef TEMPLATE_HDR_H
#define TEMPLATE_HDR_H

/* Include headers */

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

/* File content here */

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* TEMPLATE_HDR_H */

Artistic style configuration

AStyle is a great piece of software that can
help with formatting the code based on input configuration.

This repository contains astyle-code-format.cfg file which can be used with AStyle software.

1
astyle --options="astyle-code-format.cfg" "input_path/*.c,*.h" "input_path2/*.c,*.h"

Eclipse formatter

Repository contains eclipse-ext-kr-format.xml file that can be used with
eclipse-based toolchains to set formatter options.

It is based on K&R formatter with modifications to respect above rules.
You can import it within eclipse settings, Preferences -> LANGUAGE -> Code Style -> Formatter tab.