- 排 版
- 相对独立的程序块之间、变量声明之后必须加空行。
示例:
int conn_fd;
int ret;
conn_fd = socket(AF_INET, SOCK_STREAM, 0);
if (conn_fd < 0) {
perror("socket create");
}
- 程序块要采用缩进风格编写,缩进为4个空格,建议尽量不用Tab键。
如上个示例中,perror缩进了4个空格,这样可以增加程序的可读性。
- 对于较长的语句(超过个80字符)要分成多行书写,划分出的新行要进行适当的缩进,使排版整齐,语句可读。对于参数较长的函数也要划分成多行。
示例:
ret = connect(conn_fd, (struct sockaddr *)&serv_addr,
sizeof (struct sockaddr));
......
if ((very_longer_variable1 >= very_longer_variable12)
&&(very_longer_variable3 <= very_longer_variable14)
&&(very_longer_variable5 <= very_longer_variable16)){
......
}
......
- 一行只写一条语句,不允许把多个短语句写在一行中。
示例:
以下语句是不规范的:
min_port = 1; max_port = 65535;
应该如下书写:
min_port = 1;
max_port = 65535;
- if、for、do、while、case、switch、default等语句各自占一行,且if、for、do、while等语句的执行语句部分无论多少都要加括号{ }。
以下语句是不规范的:
if (conn_fd < 0) perror("socket create");
应该如下书写:
if (conn_fd < 0) {
perror("socket create");
}
- 函数内的语句、结构的定义、循环和if语句中的代码都要采用缩进风格,case语句后的处理语句也要缩进。
示例:
typedef struct _port_segment {
struct in_addr dest_ip; // struct相对于typedef缩进4个字符
unsigned short int min_port;
unsigned short int max_port;
} port_segment;
if (conn_fd < 0) {
perror("socket create"); // perror缩进4个字符
}
for (i = portinfo.min_port; i <= portinfo.max_port; i++) {
serv_addr.sin_port = htons(i);// serve_addr.sin_port缩4字符
}
- 程序块的两个分界符(C语言中为‘{’和‘}’)应独占一行并且位于同一列。或者‘{’位于上一行的行末,此时‘}’与‘{’所在行的行首对齐,‘{’前至少有一个空格。
以下代码中‘{’和‘}’各自占一行,且位于同一列:
for (i=1; i<argc; i++)
{
...
}
或者在代码中‘{’与for语句同行,‘{’与其前面的‘}’有一个空格,‘}’与‘{’语句所在行的行首对齐:
for (i = 1; i < argc; i++) {
...
}
建议函数程序块用前面一种风格,其他语句使用后面一种风格。
int copy_process(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
int pid)
{
......
}
同时注意else的位置:
if (x == y) {
..
} else if (x > y) {
...
} else {
....
}
- 空格的使用
- 以下语句在逗号后面加空格。
int i, j;
- "+"、"-"、"*"、"="等算术运算符两边都有一个空格。
a = i + j;
- "<"、">="等比较操作符两边都有一个空格。
if (conn_fd < 0) {
- "!"、"~"、"++"、"--"、"&"(地址运算符)等单目操作符前后不加空格。
i++;
- "->"、"."前后不加空格。
portinfo.min_port = i * seg_len + 1;
- if、for、while、switch等与后面的括号间应加空格,以便使if、for等关键字更为突出和明显,但不要在sizeof()、typeof、alignof或者__attribute__这些关键字之后放空格。例如,
if (conn_fd < 0)
s = sizeof(struct file);
- 不要在小括号里的表达式两侧加空格。这是一个反例:
s = sizeof( struct file );
- 当声明指针类型或者返回指针类型的函数时,“*”的首选使用方式是使之靠近变量名或者函数名,而不是靠近类型名。例子:
Char *linux_banner;
unsigned long memparse(char *ptr, char **retptr);
char *match_strdup(substring_t *s);
- 不要在行尾留空白。有些可以自动缩进的编辑器会在新行的行首加入适量的空白,然后你就可以直接在那一行输入代码。不过假如你最后没有在那一行输入代码,有些编辑器就不会移除已经加入的空白,就像你故意留下一个只有空白的行。包含行尾空白的行就这样产生了。
当git发现补丁包含了行尾空白的时候会警告你,并且可以应你的要求去掉行尾空白;不过如果你是正在打一系列补丁,这样做会导致后面的补丁失败,因为你改变了补丁的上下文。
- 注 释
- 注释的原则是有助于对程序的阅读和理解,注释不宜太多也不能太少。注释语言必须准确、易懂、简洁,没有歧义性。
- 程序文件(如以.h结尾的头文件、以.c结尾的源程序文件)头部代码应进行注释。注释必须列出:版权说明、版本号、生成日期、作者、内容、功能、与其他文件的关系、修改日志等。头文件的注释中还应有函数功能简要说明。
示例:
/*
* Copyright(C), 2007-2008, Red Hat Inc. // 版权声明
* File name: // 文件名
* Author: // 作者
* Version: // 版本
* Date: // 完成日期
* Description: // 描述本文件的功能,与其他模块的关系
* Function List: // 主要函数的列表,每条记录应包括函数名及功能简要说明
* History: // 修改历史,包括每次修改的日期、修改者和修改内容简述
*/
- 函数头部应进行注释,列出函数功能、输入参数、输出参数、返回值、调用关系等。
示例:
/*
* Description: // 函数功能、性能等的描述
* Input: // 输入参数说明,包括每个参数的作用
* Output: // 输出参数说明,有时通过指针参数返回一些变量值
* Return: // 函数返回值的说明
* Others: // 其他说明
*/
- 边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。无用的注释要及时删除。
- 注释应该在相应的代码附近,对代码的注释应放在其上方或右方(对单条语句的注释),不可放在下面,若放于上方则需与其上面的代码用空行隔开。
- 对于所有有特定含义的变量、常量、宏、结构体等数据结构,如果其命名不是充分自注释的,在声明时都必须加上注释,说明其实际含义。变量、常量、宏的注释应放在其上方或右方。
- 全局变量要有较详细的注释,包括功能,取值范围,哪些函数访问它,访问时的注意事项。
- 为使程序排版整齐,方便阅读和理解,注释也要进行缩进和对齐。
示例:
void example_function( void )
{
/* comments one */
unsigned int min_port, max_port;
/* comments two */
if ...
}
- 对关键变量的定义、条件分支、循环语句必须写注释。这些语句往往是程序实现某一特定功能的关键代码,良好的注释能帮助理解程序,有时甚至优于看设计文档。
- 避免在一行代码的中间插入注释。
以下方式的注释应该避免:
max_port /* 只扫描1~1024的常用端口 */ = 1024;
- 对于含义非常明确的变量、数据结构、语句,不必加上注释。
- 标识符、变量、宏、表达式及命名规则
- 对于标识符的命名,要有自己的风格,一旦形成不可随意变更,除非团队项目开发中要求使用统一的风格。
- 标识符的命名要清晰明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。
示例:
temp可以简写为tmp
message可以简写为msg
- 对于变量命名,禁止使用单个字符(如i、j、k),建议除了要有具体含义外,还能表明其数据类型等,但i、j、k作为局部循环变量是允许的。
示例:
int iwidth; // i表明该变量为int型,width指明是宽度
- 命名规范必须与所使用的系统风格保持一致,如在Linux下变量命名一般是全小写加下划线的风格。
一般使用:
int min_port;
一般不使用:
int minPort;
- 除了头文件或结构体定义,应避免使用_ourhead_h_之类以下划线开始和结尾的定义。
以下是允许的:
#ifndef _ourhead_h_
#define _ourhead_h_
...
#endif
typedef struct _port_segment {
struct in_addr dest_ip;
unsigned short int min_port;
unsigned short int max_port;
unsigned short int int int max_port;
} port_segment;
- 注意运算符的优先级,并用括号明确表达式的操作顺序。
示例:
if ((a | b) < (c & d))
- 避免使用不易理解的数字,用有意义的标识来替代。对于常量,不应直接使用数字,必须用有意义的枚举或宏来代替。
示例:
#define BUFF_SIZE (1024)
input_data = (char *)malloc(BUFF_SIZE);
而应避免出现类似以下的代码:
p = (char *)malloc(1024);
- 不要使用难懂的技巧性很高的语句,除非很有必要时。
示例:
不应出现类似以下的代码:
count ++ += 1;
而应改为:
count += 1;
count++;
- 尽量避免使用全局变量,全局变量增大了模块间的耦合性,不利于软件维护。
- 使用全局变量时,应明确其含义、作用、取值范围。明确全局变量与操作此变量的函数的关系,如创建、访问、修改。
- 在多线程程序中使用全局变量,应注意对变量操作的原子性。
- 应避免局部变量与全局变量同名。
- 严禁使用未经初始化的变量作为右值。在C程序中,引用未经赋值的指针,经常会引起程序崩溃。
以下代码在Linux下将导致错误,原因在于:没有使p_string指向某个内存空间的情况下,即对其进行操作是错误的。
char *p_string;
p_sting[0] = ‘a’;
应先进行初始化:
char *p_string;
p_string = (char *)malloc(BUFF_SIZE); // 这里假设BUFF_SIZE已定义
p_sting[0] = ‘a’;
- 宏要大写,用宏定义表达式时,要使用完备的括号。
如下定义的宏存在一定的风险:
#define GET_AREA(a,b) a*b
应该定义为:
#define GET_AREA(a,b) ((a)*(b))
- 若宏中有多条语句,应该将这些语句放在一对大括号中。
下面语句中只有宏的第一条表达式被执行。
#define INTI_RECT_VALUE( a, b )\
a = 0;\
b = 0;
for (index = 0; index < RECT_TOTAL_NUM; index++)
INTI_RECT_VALUE( rect.a, rect.b );
正确的用法应为:
#define INTI_RECT_VALUE( a, b ) {\
a = 0;\
b = 0;\
}
for (index = 0; index < RECT_TOTAL_NUM; index++) {
INTI_RECT_VALUE( rect[index].a, rect[index].b );
- 程序中不要出现仅靠大小写区分的相似的标识符。
- if 判断语句
- 布尔变量与0值比较:不可将布尔变量直接与TRUE 、FALSE或者1、0进行比较。用 !FLAG, 不良风格: flag == ture flag ==1 flag ==0 flag== false
- 整型变量与0值比较: 应当将整型变量用'=='或者‘!=' 直接与0比较;
- 浮点变量与0值比较不可将浮点变量用“== ”或“!="与 任何数字比较。设法转化成 x-0…>= 或者 x-0…<= 一个范围;
- 指针变量与0值比较:应当将指针变量用’==‘或者’!=‘与NULL比较P。
- 变量的名字应当使用'名词‘或者’形容词+名词“。
- 全局函数的名字应当使用‘动词’或者‘动词+名词’。
- 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。
- 尽量避免名字中出现数字编号。
- 变量和参数用小写字母开头的单词组合而成。
- 常量全用大写的字母用下划线分割单词。
- 静态变量前面加 s_
- 全局变量前加前缀g_
- 函 数
- 一个函数完成一个特定的功能,不应尝试在一个函数中实现多个不相关的功能。
- 检查函数所有输入参数的有效性,比如指针型参数要判断是否为空,数组成员参数判断是否越界。
- 一个函数的规模应限制在200行以内(不包括空行和注释行)。
- 函数的功能应该是可以预测的,也就是只要输入数据相同就应产生同样的预期输出。
- 函数的参数不宜过多,以1~3个为宜。
- 函数名应准确描述函数的功能,一般以动词加宾语的形式命名。
示例:
void print_record(struct *p_record, int record_len);
- 函数的返回值要清楚、明了,让使用者不容易忽视错误情况。函数的每种出错返回值的意义要清晰、明确,防止使用者误用,理解错误或忽视错误返回码。
函数可以返回很多种不同类型的值,最常见的一种是表明函数执行成功或者失败的值。这样的一个值可以表示为一个错误代码整数(-Exxx=失败,0=成功)或者一个“成功”布尔值(0=失败,非0=成功)。
如果函数的名字是一个动作或者强制性的命令,那么这个函数应该返回错误代码整数。如果是一个判断,那么函数应该返回一个“成功”布尔值。
比如,“add work”是一个命令,所以add_work()函数在成功时返回0,在失败时返回-EBUSY。类似的,因为“find pci device ”是一个判断,所以pci_dev_find()函数在成功找到一个匹配的设备时应该返回1,如果找不到时应该返回0。
所有导出(译注:EXPORT)的函数都必须遵守这个惯例,所有的公共函数也都应该如此。私有(static)函数不需要如此,但是我们也推荐这样做。
返回值是实际计算结果而不是计算是否成功的标志的函数不受此惯例的限制。一般的,他们通过返回一些正常值范围之外的结果来表示出错。典型的例子是返回指针的函数,他们使用NULL或者ERR_PTR机制来报告错误。
- 如果多段代码重复做同一件事情,那么应该考虑把重复功能实现为一个函数。
- 减少函数本身或函数间的递归调用。
递归调用特别是函数间的递归调用(如A->B->C->A),影响程序的可理解性;递归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试不利。
- 编写函数时应注意提高函数的独立性,尽量减少与其他函数的联系;提高代码可读性、可维护性和效率。
- 程序效率
- 编程时要经常注意代码的效率。
代码效率分为全局效率、局部效率、时间效率及空间效率。全局效率是站在整个系统角度上的效率;局部效率是站在模块或函数角度上的效率;时间效率是程序处理输入任务所需的时间长短;空间效率是程序所需内存空间,如机器代码空间大小、数据空间大小、栈空间大小等。
- 在保证软件系统的正确性、稳定性、可读性及可维护性的前提下,提高代码效率。但不能一味地追求代码效率,而对软件的正确性、稳定性、可读性及可维护性造成损害。
- 通过对数据结构的划分与组织的改进,以及对程序算法的优化来提高空间效率。
- 循环体内的工作量应最小化。
如下代码效率不高:
for (ind = 0; ind < MAX_ADD_NUMBER; ind++) {
sum += ind;
back_sum = sum; /* backup sum */
}
语句“back_sum = sum;”完全可以放在for语句之后,如下:
for (ind = 0; ind < MAX_ADD_NUMBER; ind++) {
sum += ind;
}
back_sum = sum; /* backup sum */
- 要仔细地构造或直接用汇编编写调用频繁或性能要求极高的函数。
- 在保证程序质量的前提下,通过压缩代码量,去掉不必要代码以及减少不必要的局部和全局变量,来提高空间效率。
- 在多重循环中,应将最忙的循环放在最内层,以减少CPU切入循环层的次数。
如下代码效率较低:
for (row = 0; row < 100; row++) {
for (col = 0; col < 5; col++) {
sum += a[row][col];
}
}
可以改为如下方式,以提高效率:
for (col = 0; col < 5; col++) {
for (row = 0; row < 100; row++) {
sum += a[row][col];
}
}
- 避免循环体内含判断语句,应将循环语句置于判断语句的代码块之中。
如下代码效率较低:
for (ind = 0; ind < MAX_RECT_NUMBER; ind++) {
if (data_type == RECT_AREA) {
area_sum += rect_area[ind];
}
else {
rect_length_sum += rect[ind].length;
rect_width_sum += rect[ind].width;
}
}
因为判断语句与循环变量无关,可作如下改进,以减少判断次数:
if (data_type == RECT_AREA) {
for (ind = 0; ind < MAX_RECT_NUMBER; ind++) {
area_sum += rect_area[ind];
}
}
else {
for (ind = 0; ind < MAX_RECT_NUMBER; ind++) {
rect_length_sum += rect[ind].length;
rect_width_sum += rect[ind].width;
}
}
- 代码质量和安全
- 代码质量和代码安全包含以下内容:代码的正确性(实现预期的功能)、稳定性、安全性、可测试性、可维护性、可读性和效率。
- 防止引用已经释放的内存空间。
在实际编程过程中,稍不留心就会出现在一个模块中释放了某个内存块,而另一模块在随后的某个时刻又引用了它,要防止这种情况发生。
- 函数中分配的内存,在函数结束前要释放。
- 防止内存操作越界。内存操作主要是指对数组、指针、内存地址等的操作。内存操作越界是软件系统主要的错误之一,后果是使运行中的程序崩溃,或者留下安全漏洞。
以下代码对array[10]进行了操作,导致数组越界访问:
int array[10], i;
for(i = 1; i <=10; i++) {
array[i] = 10;
}
对于类似的错误,编译器不能检测出,认为合法。
- 处理程序可能遇到的各种出错情况。
例如,打开一个文件时要考虑文件是否存在,是否有权限访问等。
- 系统运行之初,要初始化有关变量及运行环境,防止未经初始化的变量被引用。
- 程序编写完成后,应该检查易混淆的操作符,如“==”和“=”、“&&”和“&”、“||”和“|”。
- Linux下,多线程中的子线程退出必须采用主动退出方式,即子线程应在return处结束运行。
- 尽量避免使用goto语句。goto语句会破坏程序的结构性,除非确实需要。
- 属于本模块内的函数及变量,尽量使用static来定义。
- 注意表达式是否会上溢、下溢。
示例:
unsigned char size = 5;
while (size-- >= 0) // 将出现下溢
{
... // 程序代码
}
当size等于0时,再减1值为-1。系统中-1表示为0xFF,255也表示为0xFF,由于size为无符号数,系统认为该值为255,故程序是一个死循环。
应作如下修改:
char size; // 从unsigned char 改为char
while (size-- >= 0)
{
... // 程序代码
}
- 每次提交代码要尽量消除因本次修改所引起的编译警告等信息。
- 打印信息要统一,发布时尽量屏蔽调试信息,推荐如下格式:
#ifdef PRINT_DEBUG
printf(“%s:%s:%d:Program run to here!”__FILE__,__func__,__LINE__);
#endif
- 系统应具有一定的容错能力,对一些错误事件(如用户误操作)能进行自动补救。
- Linux kernel 代码
1) 内核代码应严格按照linux 内核编码规范进行:
https://www.kernel.org/doc/Documentation/process/coding-style.rst
2) 生成好的patch必须通过内核代码校验脚本校验。