缩进和空白

空白

  1. 关键字if、while、for与其后的控制表达式的(括号之间插入一个空格分隔,但括号内的表达式应紧贴括号。例如:
1
while␣(1);
  1. 双目运算符的两侧各插入一个空格分隔,单目运算符和操作数之间不加空格,例如i␣=␣i␣+␣1++i!(i␣<␣1)-x&a[1]等。
  2. 后缀运算符和操作数之间也不加空格,例如取结构体成员s.a、函数调用foo(arg1)、取数组成员a[i]
  3. ,号和;号之后要加空格,这是英文的书写习惯,例如for␣(i␣=␣1;␣i␣<␣10;␣i++)foo(arg1,␣arg2)
  4. 以上关于双目运算符和后缀运算符的规则并没有严格要求,有时候为了突出优先级也可以写得更紧凑一些,例如for␣(i=1;␣i<10;␣i++)distance␣=␣sqrt(x*x␣+␣y*y)等。但是省略的空格一定不要误导了读代码的人,例如a||b␣&&␣c很容易让人理解成错误的优先级。
  5. 由于UNIX系统标准的字符终端是24行80列的,接近或大于80个字符的较长语句要折行写,折行后用空格和上面的表达式或参数对齐,例如:
1
2
3
if␣(sqrt(x*x␣+␣y*y)␣>␣5.0
&&␣x␣<␣0.0
&&␣y␣>␣0.0)

再比如:

1
2
foo(sqrt(x*x␣+␣y*y),
a[i-1]␣+␣b[i-1]␣+␣c[i-1])
  1. 较长的字符串可以断成多个字符串然后分行书写,例如:
1
2
printf("This is such a long sentence that "
"it cannot be held within a line\n");

C编译器会自动把相邻的多个字符串接在一起,以上两个字符串相当于一个字符串"This is such a long sentence that it cannot be held within a line\n"。

  1. 有的人喜欢在变量定义语句中用Tab字符,使变量名对齐,这样看起来很美观。
1
2
int    →a, b;
double →c;

缩进

内核代码风格关于缩进的规则有以下几条。

  1. 要用缩进体现出语句块的层次关系,使用Tab字符缩进,不能用空格代替Tab。在标准的字符终端上一个Tab看起来是8个空格的宽度,如果你的文本编辑器可以设置Tab的显示宽度是几个空格,建议也设成8,这样大的缩进使代码看起来非常清晰。如果有的行用空格做缩进,有的行用Tab做缩进,甚至空格和Tab混用,那么一旦改变了文本编辑器的Tab显示宽度就会看起来非常混乱,所以内核代码风格规定只能用Tab做缩进,不能用空格代替Tab。
  2. if/else、while、do/while、for、switch这些可以带语句块的语句,语句块的{或}应该和关键字写在同一行,用空格隔开,而不是单独占一行。例如应该这样写:
1
2
3
4
5
if␣(...)␣{
→语句列表
}␣elseif␣(...)␣{
→语句列表
}
  1. 函数定义的{}单独占一行,这一点和语句块的规定不同,例如:
1
2
3
4
int␣foo(int␣a,␣int␣b)
{
→语句列表
}
  1. switch和语句块里的case、default对齐写,也就是说语句块里的case、default标号相对于switch不往里缩进,但标号下的语句要往里缩进。例如:
1
2
3
4
5
6
7
8
switch␣(c)␣{
case 'A':
→语句列表
case 'B':
→语句列表
default:
→语句列表
}

用于goto语句的自定义标号应该顶头写不缩进,而不管标号下的语句缩进到第几层。

  1. 代码中每个逻辑段落之间应该用一个空行分隔开。例如每个函数定义之间应该插入一个空行,头文件、全局变量定义和函数定义之间也应该插入空行,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>

int g;
double h;

int foo(void)
{
→语句列表
}

int bar(int a)
{
→语句列表
}

int main(void)
{
→语句列表
}
  1. 一个函数的语句列表如果很长,也可以根据相关性分成若干组,用空行分隔。这条规定不是严格要求,通常把变量定义组成一组,后面加空行,return语句之前加空行,例如:
1
2
3
4
5
6
7
8
9
10
11
int main(void)
{
int →a, b;
double →c;

→语句组1

→语句组2

return 0;
}

注释

单行注释应采用/*␣comment␣*/的形式,用空格把界定符和文字分开。

伟洲注: 关于注释应该使用 /*...*/ 还是 // 具体要看项目的需求。Linux Kernel因为要考虑平台兼容性,不允许使用C99的//注释。但如果程序并不会兼容性有过多要求,使用//进行注释也是个很好的选择。

多行注释最常见的是这种形式:

1
2
3
4
/*
␣*␣Multi-line
␣*␣comment
␣*/

也有更花哨的形式:

1
2
3
4
/*************\
* Multi-line *
* comment *
\*************/

使用注释的场合主要有以下几种。

整个源文件的顶部注释。说明此模块的相关信息,例如文件名、作者和版本历史等,顶头写不缩进。例如内核源代码目录下的kernel/sched.c文件的开头:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* kernel/sched.c
*
* Kernel scheduler and related syscalls
*
* Copyright (C) 1991-2002 Linus Torvalds
*
* 1996-12-23 Modified by Dave Grothe to fix bugs in semaphores and
* make semaphores SMP safe
* 1998-11-19 Implemented schedule_timeout() and related stuff
* by Andrea Arcangeli
* 2002-01-04 New ultra-scalable O(1) scheduler by Ingo Molnar:
* hybrid priority-list and round-robin design with
* an array-switch method of distributing timeslices
* and per-CPU runqueues. Cleanups and useful suggestions
* by Davide Libenzi, preemptible kernel bits by Robert Love.
* 2003-09-03 Interactivity tuning by Con Kolivas.
* 2004-04-02 Scheduler domains code by Nick Piggin
*/
  1. 函数注释。说明此函数的功能、参数、返回值、错误码等,写在函数定义上侧,和此函数定义之间不留空行,顶头写不缩进。
  2. 相对独立的语句组注释。对这一组语句做特别说明,写在语句组上侧,和此语句组之间不留空行,与当前语句组的缩进一致。
  3. 代码行右侧的简短注释。对当前代码行做特别说明,一般为单行注释,和代码之间至少用一个空格隔开,一个源文件中所有的右侧注释最好能上下对齐。内核源代码目录下的lib/radix-tree.c文件中的一个函数包含了上述三种注释:
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
/**
* radix_tree_insert - insert into a radix tree
* @root: radix tree root
* @index: index key
* @item: item to insert
*
* Insert an item into the radix tree at position @index.
*/
int radix_tree_insert(struct radix_tree_root *root,
unsigned long index, void *item)
{
struct radix_tree_node *node = NULL, *slot;
unsigned int height, shift;
int offset;
int error;

/* Make sure the tree is high enough. */
if ((!index && !root->rnode) ||
index > radix_tree_maxindex(root->height)) {
error = radix_tree_extend(root, index);
if (error)
return error;
}

slot = root->rnode;
height = root->height;
shift = (height-1) * RADIX_TREE_MAP_SHIFT;

offset = 0; /* uninitialised var warning */
do {
if (slot == NULL) {
/* Have to add a child node. */
if (!(slot = radix_tree_node_alloc(root)))
return -ENOMEM;
if (node) {
node->slots[offset] = slot;
node->count++;
} else
root->rnode = slot;
}

/* Go a level down */
offset = (index >> shift) & RADIX_TREE_MAP_MASK;
node = slot;
slot = node->slots[offset];
shift -= RADIX_TREE_MAP_SHIFT;
height--;
} while (height > 0);

if (slot != NULL)
return -EEXIST;

BUG_ON(!node);
node->count++;
node->slots[offset] = item;
BUG_ON(tag_get(node, 0, offset));
BUG_ON(tag_get(node, 1, offset));

return 0;
}
  1. 复杂的结构体定义比函数更需要注释。例如内核源代码目录下的kernel/sched.c文件中定义了这样一个结构体:
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
/*
* This is the main, per-CPU runqueue data structure.
*
* Locking rule: those places that want to lock multiple runqueues
* (such as the load balancing or the thread migration code), lock
* acquire operations must be ordered by ascending &runqueue.
*/
struct runqueue {
spinlock_t lock;

/*
* nr_running and cpu_load should be in the same cacheline because
* remote CPUs use both these fields when doing load calculation.
*/
unsigned long nr_running;
#ifdef CONFIG_SMP
unsigned long cpu_load[3];
#endif
unsigned long long nr_switches;

/*
* This is part of a global counter where only the total sum
* over all CPUs matters. A task can increase this counter on
* one CPU and if it got migrated afterwards it may decrease
* it on another CPU. Always updated under the runqueue lock:
*/
unsigned long nr_uninterruptible;

unsigned long expired_timestamp;
unsigned long long timestamp_last_tick;
task_t *curr, *idle;
struct mm_struct *prev_mm;
prio_array_t *active, *expired, arrays[2];
int best_expired_prio;
atomic_t nr_iowait;

#ifdef CONFIG_SMP
struct sched_domain *sd;

/* For active balancing */
int active_balance;
int push_cpu;

task_t *migration_thread;
struct list_head migration_queue;
int cpu;
#endif

#ifdef CONFIG_SCHEDSTATS
/* latency stats */
struct sched_info rq_sched_info;

/* sys_sched_yield() stats */
unsigned long yld_exp_empty;
unsigned long yld_act_empty;
unsigned long yld_both_empty;
unsigned long yld_cnt;

/* schedule() stats */
unsigned long sched_switch;
unsigned long sched_cnt;
unsigned long sched_goidle;

/* try_to_wake_up() stats */
unsigned long ttwu_cnt;
unsigned long ttwu_local;
#endif
};
  1. 复杂的宏定义和变量声明也需要注释。例如内核源代码目录下的include/linux/jiffies.h文件中的定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* TICK_USEC_TO_NSEC is the time between ticks in nsec assuming real ACTHZ and  */
/* a value TUSEC for TICK_USEC (can be set bij adjtimex) */
#define TICK_USEC_TO_NSEC(TUSEC) (SH_DIV (TUSEC * USER_HZ * 1000, ACTHZ, 8))

/* some arch's have a small-data section that can be accessed register-relative
* but that can only take up to, say, 4-byte variables. jiffies being part of
* an 8-byte variable may not be correctly accessed unless we force the issue
*/
#define __jiffy_data __attribute__((section(".data")))

/*
* The 64-bit value is not volatile - you MUST NOT read it
* without sampling the sequence number in xtime_lock.
* get_jiffies_64() will do this for you as appropriate.
*/
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

命名

变量命名

普通变量命名

内核编码风格规定变量、函数和类型采用全小写加下划线的方式命名。变量的名字应当使用“名词”或者“形容词+名词”。

1
2
string table_name;  // OK - uses underscore.
string tableName; // Bad - mixed case.

全局变量

如果不得已需要全局变量,则使全局变量加前缀 g_(表示 global)。例如:

1
2
int g_how_many_people;    // 全局变量
int g_how_much_money; // 全局变量

静态变量

静态变量加前缀 s_(表示 static)。例如:

1
2
3
4
5
void Init(...)
{
static int s_init_value; // 静态变量
...
}

常量命名

常量(比如宏定义和枚举常量)采用全大写加下划线的方式命名:

const 常量

1
const int DAYS_IN_A_WEEK = 7;

通常 不应该 使用宏. 如果不得不用, 其命名像常量一样全部大写, 使用下划线:

1
2
#define ROUND(x) ...
#define PI_ROUNDED 3.0

枚举常量

1
enum coordinate_type { RECTANGULAR, POLAR };

函数、类型命名

常规函数

统一小写加下划线,和变量的命名方式相同。

1
2
3
4
5
6
7
8
void add_table_entry();
int delete_url();

struct complex_struct{
double x, y;
};

enum coordinate_type { RECTANGULAR, POLAR };
Modern C++ 则建议函数名的每个单词首字母大写, 没有下划线。这种大小写混合的命名方式在c++代码中很普遍,称为 camelcase(驼峰命名法),大概是因为有高有低像驼峰一样。

深入阅读

  1. 编码风格
  2. Linux Coding Style

Comments