C代码风格

来源:互联网 发布:魅可mac什么档次 编辑:程序博客网 时间:2024/04/30 08:39

转自:http://blog.sina.com.cn/s/blog_5027969401007sxr.html


代码风格是一个很个性化的东西,每个人都会有自己的喜好和见解。这里列出的是我个人的风格,并且是一般的代码风格。所谓一般是指文中没有对标识符的命名有太多的规定,如全局变量、局部变量、宏等。相关规则一般在具体的项目中给出,不同的项目可以有不同的命名规则。

 

屏幕空间:这里基于标准的UNIX终端(Terminal)来定义屏幕的大小,宽度为80个字符,高度为24或25行。

 

 

1 缩进

1.1 基本规则

使用8字符宽度的tab来控制缩进。除了注释,空格从来不用于缩进;相应的,tab只用于缩进,不用于其他场合。

缩进(indentation)的目的是为了清楚的表现一个逻辑块的开始和结束,使用8字符这样的大缩进可以表现得更明显。

关于缩进的风格有很多,其中反对tab的不在少数。反对的理由之一是tab在不同的系统和编辑器上可能有不同的定义,从而导致本来很规整的代码在别的系统上显示错位。这确实是一个问题,所以,开始之前,请确认使用的编辑器将tab设置等于8字符宽度。

反对使用8字符tab的另一理由是,当缩进层次太多时,代码向屏幕右侧跑得太快,导致跨行代码增多,难于阅读。确实,在一些复杂的商业逻辑中,缩进层次可能很多。对此这里引用Linux Kernel代码风格中的一个解释:如果你的代码有超过3层的缩进,那么你需要重新设计你的程序。

要做到这点不容易,尤其当程序员水平有限,或者项目紧急,没有时间来优化代码。此时使用别的缩进方案(如4空格缩进)可能是一个折中的方案。

 

1.2 基本形式

以if语句为例:

if (mapping) spin_lock(&mapping->i_shared_lock);

不要写在一行上:

if (mapping) spin_lock(&mapping->i_shared_lock);

类型定义:

struct nlmsgerr { int error; struct nlmsghdr msg; };

 

1.3 switch语句

switch语句稍微有点例外,每个case标号与switch关键字在同一个缩进层次:

switch (behavior) { case MADV_SEQUENTIAL: vma->vm_flags |= VM_SEQ_READ; break; case MADV_RANDOM: vma->vm_flags |= VM_RAND_READ; break; default: break; }

 

 

2 空格

2.1 关键字

在多数情况下,关键字后面用一个空格来与其他代码分开,比如if、switch、case、for、do、while等。

for (i = 0; i < smp_num_cpus; i++) while (!cachep->growing) do { ... } while (sizes->cs_size);

sizeof和defined例外,在sizeof和defined后面使用小括号,不使用空格,虽然C语法并没有如此强制规定。如:

base = sizeof(slab_t); #if defined(CONFIG_SMP)

 

2.2 括号

函数名和括号之间没有空格。

在小括号与内部的表达式之间也没有空格。如:

memset(addr, POISON_BYTE, size);

而不是:

memset( addr, POISON_BYTE, size );

该规则同样适用于中括号,以及用于初始化列表的大括号。如:

char buf[20 + 40]; cpucache_t *new[NR_CPUS]; struct swap_list_t swap_list = {-1, -1};

当括号中的内容为空时,左右括号之间也没有空格。如:

static const char bad_file[] = "Bad swap file entry ";

 

2.3 指针

当声明指针类型的数据和返回指针类型的函数时,星号(*)与数据名和函数名相连,而不是与类型名相连。如:

slab_t *slabp; static void *s_next(struct seq_file *m, void *p, loff_t *pos)

 

2.4 二元运算符

在二元运算符,诸如= + - < > * / % | & ^ <= >= == !=等的两边各用一个空格:

slabp = objp + colour_off; objp -= BYTES_PER_WORD; if (cachep->colour_next >= cachep->colour) if ((flags & SLAB_POISON) && ctor)

 

2.5 三元运算符

三元运算符?:因形式的特殊性,总共用到4个空格(?和:两边各一个):

(gfpflags & GFP_DMA) ? csizep->cs_dmacachep : csizep->cs_cachep;

 

2.6 一元运算符

在一元运算符,诸如& * + - ~ ! sizeof defined等的后面不加空格:

address &= ~PGDIR_MASK; if (!offset) static kmem_cache_t *clock_searchp = &cache_cache;

在后缀形式的++和--前不加空格:

pte++; count--;

在前缀形式的++和--后不加空格:

++pte; --count;

在结构运算符.和->的两边不加空格:

area->flags = flags; spin_lock(&init_mm.page_table_lock);

 

2.7 强制转换

在强制转换后面不加空格:

area->addr = (void *)addr;

 

2.8 分号和逗号

分号(;)与前面的代码之间不加空格。

当分号用于for语句头部时,与后面的代码(右括号除外)之间加一个空格。

for (p = &vmlist; (tmp = *p); p = &tmp->next)

当for用于永真循环时,则没有空格:

for (;;)

逗号(,)规则与上类似。当一个初始化列表以逗号结束时,逗号与右大括号之间可以有一个空格:

int acct_parm[] = {4, 2, 30, };

在每行的末尾不要遗留多余的空白字符。如($表示行的结束):

if (niceval < -20)$

而不是:

if (niceval < -20) $

 

2.9 对齐

在多行变量定义的地方,在类型与变量名之间可以适当的增加空格来对齐(注意不能用tab,tab只用于缩进!):

int size; fd_set *new_fdset;

该规则也适用于其他一些类似的场合,如宏定义:

#define STATS_INC_FREEHIT(x) do { } while (0) #define STATS_INC_FREEMISS(x) do { } while (0)

 

 

3 大括号的位置

3.1 语句块

这里采用Kernighan和Ritchie的风格(K&R):左括号({)在行的末尾,与前面代码之间有一个空格;右括号(})则单独成行,并与对应的块起始行对齐。即:

while (--n) { p = p->next; if (p == &cache_cache.next) return NULL; }

注意右括号是单独成行,除非后面还有子句,如do-while、if-else等。此时在右括号和后面的关键字之间加一个空格。形式如下:

do { ... } while (--i); if (end == area->vm_end) { ... } else if (addr == area->vm_start) { ... } else { ... }

注意以上多路if-else的缩进格式,不要写成下面的形式:

if (end == area->vm_end) { ... } else if (addr == area->vm_start) { ... } else { ... }

该规则对所有语句块(除了函数)都适用,包括if、switch、for、while、do等。

这样做的理由是可以大幅度减少括号单独形成的行的的数目,从而在屏幕(比如24行的终端)中可以容纳下更多的代码,同时不损失代码的可读性。

 

3.2 函数定义

对于函数定义,左括号和右括号都单独成行:

void lock_vma_mappings(struct vm_area_struct *vma) { struct address_space *mapping; mapping = NULL; if (vma->vm_file) mapping = vma->vm_file->f_dentry->d_inode->i_mapping; if (mapping) spin_lock(&mapping->i_shared_lock); }

 

 

4 长行代码

每行代码的长度原则上不能超过屏幕宽度,即80个字符。

超过80个字符的行应分成多行书写,后续行的长度不大于前面行的长度,并缩进放置在右侧。这规则也适用于长参数列表的函数和长字符串。如:

printk(KERN_WARNING "Warning this is a long printk with " "3 parameters a: %u b: %u " "c: %u \n", a, b, c);

当函数参数太多时,出于可读性和维护性的考虑,也可采用如下方式:

if (msq != NULL) { len += sprintf(buffer + len, "%10d %10d %4o %10lu %10l u %5u %5u %5u %5u %5u %5u %10lu %10lu %10lu\n", msq->q_perm.key, msg_buildid(i, msq->q_perm.seq), msq->q_perm.mode, msq->q_cbytes, msq->q_qnum, msq->q_lspid, msq->q_lrpid, msq->q_perm.uid, msq->q_perm.gid, msq->q_perm.cuid, msq->q_perm.cgid, msq->q_stime, msq->q_rtime, msq->q_ctime);

对于长字符串,如果觉得以上规则妨碍了字符串格式的控制,那么也可以超过80字符,如上例,但这样的情形不应该出现太多。

 

 

5 注释

采用“”方式注释,不允许嵌套,也不允许C99风格注释“//...”。

多行注释的格式:

 

 

6 函数

6.1 定义

多个函数定义之间用一个空行分隔:

void lock_vma_mappings(struct vm_area_struct *vma) { struct address_space *mapping; mapping = NULL; if (vma->vm_file) mapping = vma->vm_file->f_dentry->d_inode->i_mapping; if (mapping) spin_lock(&mapping->i_shared_lock); } void unlock_vma_mappings(struct vm_area_struct *vma) { struct address_space *mapping; mapping = NULL; if (vma->vm_file) mapping = vma->vm_file->f_dentry->d_inode->i_mapping; if (mapping) spin_unlock(&mapping->i_shared_lock); }

推荐将函数返回类型与函数名分成两行,使函数名顶头书写:

static void remove_shared_vm_struct(struct vm_area_struct *vma) { lock_vma_mappings(vma); __remove_shared_vm_struct(vma); unlock_vma_mappings(vma); }

这样做的目的是,可以方便使用grep等工具,利用正则表达式,在很多源文件中快速定位某个函数定义所在的文件。

 

6.2 声明

在函数的声明(prototype)中保留参数的名字。虽然语法上这可以省略,但可以增强可读性:

extern long vread(char *buf, char *addr, unsigned long count);

而不是写成这样:

extern long vread(char *, char *, unsigned long);

 

6.3 返回值

有两类典型的函数返回值类型:

  1. 返回0表示成功,非0作为错误码返回并表示失败;
  2. 作为布尔值返回,非0表示成功,0表示失败。

为避免这种不统一造成混淆,规定如下:

  • 如果函数行为是一些操作和命令,则使用第一种类型返回值;
  • 如果函数行为是一判断,则使用第二种类型返回值。

对于其他返回实际结果的函数不在这限制之列。例如,指针类型的返回值,非NULL代表具体内容,NULL表示失败。

 

6.4 函数体

函数内部,变量定义部分与代码部分中间隔一空行。

代码之间根据逻辑关系可以加空行来分隔。

return与返回值之间加一空格,除非函数类型为void,此时return后面直接为分号。返回值不需要用括号括住,除非返回表达式很复杂,影响了可读性。

示例:

static int is_chained_kmem_cache(kmem_cache_t *cachep) { struct list_head *p; int ret = 0; down(&cache_chain_sem); list_for_each(p, &cache_chain) { if (p == &cachep->next) { ret = 1; break; } } up(&cache_chain_sem); return ret; }

 

 

7 预处理和枚举

7.1 基本规则

使用大写字母来命名宏常量和枚举中的符号。当定义一组相关的常量时,使用枚举比宏常量更合适。

“#”与include、define等之间没有空白,include、define等与后面的代码加一个空格(存在的话)。

预处理符号都是顶头书写,不缩进,即使在复杂的分支结构中:

#include <stdio.h> #include "myheader.h" #define MAXSIZE 100 #ifdef CONFIG_DEBUG_SLAB #define DEBUG 1 #define STATS 1 #define FORCED_DEBUG 1 #else #define DEBUG 0 #define STATS 0 #define FORCED_DEBUG 0 #endif

宏函数用小写字母来命名。注意使用括号来括住相关的表达式:

#define cc_entry(cpucache) ((void **)(((cpucache_t *)(cpucache)) + 1))

 

7.2 多语句宏

由多条操作语句组成的宏可使用do-while结构。如:

# define CHECK_PAGE(page) \ do { \ CHECK_NR(page); \ if (!PageSlab(page)) { \ printk(KERN_ERR "kfree: bad ptr %lxh.\n", \ (unsigned long)objp); \ BUG(); \ } \ } while (0)

 

7.3 宏的禁止写法

禁止以如下方式使用宏:

(1).改变程序流程:

#define FOO(x) \ do { \ if (blah(x) < 0) \ return -EBUGGERED; \ } while(0)

(2).依赖于本地变量:

#define FOO(val) bar(index, val)

(3).将宏函数作为左值:

FOO(x) = y;

 

 

8 标号与goto

8.1 命名规则

标号使用大写字母命名,标号与后面的冒号(:)之间没有空格。

标号不缩进,顶头书写,单独成行。

 

8.2 goto的意义

goto有它存在的必要,在某些场合比非goto实现更合理。比如:

  • 从多层嵌套循环中退出。
  • 一个有多个初始化步骤的函数,每步失败都需要清理之前分配的所有资源。
if (init1) goto INIT1_FAIL; if (init2) goto INIT2_FAIL; if (init3) goto INIT3_FAIL; ... INIT3_FAIL: clean3; INIT2_FAIL: clean2; INIT1_FAIL: clean1;

 

8.3 goto的限制

虽然不禁止goto语句的使用,但也不能忽视goto的危害,故有如下一些限制:

  • goto与对应的标号都在一个函数内;
  • 只能向后跳转,不能向前跳转。

 

 

9 命名

命名规则这里不做过多限定,只列出一些建议:

  • 没必要使用ThisVariableIsATemporaryCounter这类的命名,而是使用tmp这样简短、容易书写,而且不难理解的名称。
  • 当然,对于全局变量和全局函数,应该使用描述性将的命名,如count_active_users(),而不是cntusr()。
  • 局部变量在不影响理解的情况下,应该简短,比如循环变量,可使用i,没必要用loop_counter。

 

 

10 typedef

不要滥用typedef。对于一般的结构和指针等类型,没必要用typedef来重命名,保留struct/union等关键字,可读性其实更好。

使用typedef的场合:

  • 有意隐藏某些东西;
  • 需要精确的数据类型,如u8/u16/u32;
  • 屏蔽环境差异,如在一个地方需要int,在另一个地方需要long;
  • 简化一些复杂的数据类型,如signal函数的说明。
0 0
原创粉丝点击