版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
- Linux程序的命名习惯和Windows程序的命名习惯及著名的匈牙利命名法有很大的不同
一、documentation/coding文档
- Linux有独特的编码风格,在Linux内核源代码下存在一个文件cocumentation/codingStyle,进行了比较详细的描述
二、stylescripts/checkpatch.pl脚本
- 内核下的scripts/checkpatch.pl提供了1个检查代码风格的脚本
演示案例
//demo.c for(int i=0;i<10;i++){ a=c; }
在SCM软件服务端部署checkpatch.pl脚本
- 在工程阶段,一般可以在SCM软件的服务器端使能pre-commit hook,自动检查工程师提交的代码是否 符合Linux的编码风格,如果不符合,则自动拦截
- git的pre-commit hook可以运行在本地代码仓库中,下面是一个演示版本
#!/bin/sh # # pre-commit hook to run check-patch on the output and stop any commits # that do not pass. Note, only for git-commit, and not for any of the # other scenarios # # Copyright 2010 Ben Dooks, <[email protected]> if git rev-parse --verify HEAD 2>/dev/null >/dev/null then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi git diff --cached $against -- | ./scripts/checkpatch.pl --no-signoff
二、命名规范
- 名称间使用下划线,使用小写字母
三、代码缩进
- Linux的代码缩进使用“TAB”
四、空格使用
- 另外,请注意代码中空格的应用,譬如“for *(i*=*0;*i*<*10;*i++)*{” 语 句中的“*”都是空格
五、Linux中代码括号“{”和“}”的使用原则
- 1.对于结构体、if/for/while/switch语句, “{”不另起一行,例如:
- 2.如果if、for循环后只有1行,不要加“{”和“}” ,例如:
- 3.if和else混用的情况下,else语句不另起一行,例如:
- 4.对于函数, “{”另起一行,譬如:
- 5.在switch/case语句方面,Linux建议switch和case对齐,例如:
六、GNU C与ANSI C
- Linux上可用的C编译器是GNU C编译器,它建立在自由软件基金会的编程许可证的基础上,因此可以自由发布。GNU C对标准C进行一系列扩展,以增强标准C的功能
零长度和变量长度数组
- GNU C允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。
- 例如:char data[0]仅仅意味着程序中通过var_data结构体实例的data[index]成员可以访问len之后的第index个地 址,它并没有为data[]数组分配内存,因此sizeof(struct var_data)=sizeof(int)
struct var_data { int len; char data[0]; };
- 假设struct var_data的数据域就保存在struct var_data紧接着的内存区域中,则通过如下代码可以遍历这 些数据:
struct var_data s; ... for (i = 0; i < s.len; i++) printf("%02x",s.data[i]);
case范围
- GNU C支持case x…y这样的语法,区间[x,y]中的数都会满足这个case的条件
- 请看下面的代码:
switch (ch) { =case '0'... '9': c -= '0'; break; case 'a'... 'f': c -= 'a' - 10; break; case 'A'... 'F': c -= 'A' - 10; break; }
- 代码中的case'0'...'9'等价于标准C中的:
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
语句表达式
- NU C把包含在括号中的复合语句看成是一个表达式,称为语句表达式,它可以出现在任何允许表 达式的地方。我们可以在语句表达式中使用原本只能在复合语句中使用的循环、局部变量等
- 例如:
#define min_t(type,x,y) \ ({type _ _x =(x);type _ _y = (y); _ _x<_ _y _ _x: _ _y; }) int ia, ib, mini; float fa, fb, minf; mini = min_t(int, ia, ib); minf = min_t(float, fa, fb);
- 因为重新定义了__xx和__y这两个局部变量,所以用上述方式定义的宏将不会有副作用。在标准C中,对应的如下宏则会产生副作用:
- 代码min(++ia,++ib)会展开为((++ia)<(++ib)(++ia):(++ib)),传入宏的“参数”增加 两次
#define min(x,y) ((x) < (y) (x) : (y))
typeof关键字
- typeof(x)语句可以获得x的类型,因此,可以借助typeof重新定义min这个宏:
#define min(x,y) ({ \ const typeof(x) _x = (x); \ const typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x < _y _x : _y; })
- 我们不需要像min_t(type,x,y)那个宏那样把type传入,因为通过typeof(x)、typeof(y)可以获 得type。代码行(void)(&_x==&_y)的作用是检查_x和_y的类型是否一致
可变参数宏
- 标准C就支持可变参数函数,意味着函数的参数是不固定的
- 例如printf()函数的原型为:
int printf( const char *format [, argument]... );
- 而在GNU C中,宏也可以接受可变数目的参数,例如
#define pr_debug(fmt,arg...) \ printk(fmt,##arg)
- 这里arg表示其余的参数,可以有零个或多个参数,这些参数以及参数之间的逗号构成arg的值,在宏扩展时替换arg,如下列代码:
pr_debug("%s:%d",filename,line) 会被扩展为: printk("%s:%d", filename, line)
- 使用“##”是为了处理arg不代表任何参数的情况,这时候,前面的逗号就变得多余了。使用“##”之后, GNU C预处理器会丢弃前面的逗号,这样,下列代码:
pr_debug("success!\n") 会被正确地扩展为: printk("success!\n") 而不是: printk("success!\n",)
标号元素
- 标准C要求数组或结构体的初始化值必须以固定的顺序出现,在GNU C中,通过指定索引或结构体成 员名,允许初始化值以任意顺序出现
- 指定数组索引的方法是在初始化值前添加“[INDEX]=” ,当然也可以用“[FIRST...LAST]=”的形式指定 一个范围。例如,下面的代码定义了一个数组,并把其中的所有元素赋值为0:
unsigned char data[MAX] = { [0 ... MAX-1] = 0 };
- 下面的代码借助结构体成员名初始化结构体:
struct file_operations ext2_file_operations = { llseek: generic_file_llseek, read: generic_file_read, write: generic_file_write, ioctl: ext2_ioctl, mmap: generic_file_mmap, open: generic_file_open, release: ext2_release_file, fsync: ext2_sync_file, };
- 但是,Linux 2.6推荐类似的代码应该尽量采用标准C的方式:
struct file_operations ext2_file_operations = { .llseek = generic_file_llseek, .read = generic_file_read, .write = generic_file_write, .aio_read = generic_file_aio_read, .aio_write = generic_file_aio_write, .ioct = ext2_ioctl, .mmap = generic_file_mmap, .open = generic_file_open, .release = ext2_release_file, .fsync = ext2_sync_file, .readv = generic_file_readv, .writev = generic_file_writev, .sendfile = generic_file_sendfile, };
当前函数名
- GNU C预定义了两个标识符保存当前函数的名字,__FUNCTION__保存函数在源码中的名字, __PRETTY_FUNCTION__保存带语言特色的名字。在C函数中,这两个名字是相同的
void example() { printf("This is function:%s", __FUNCTION__); }
- 代码中的__FUNCTION__意味着字符串“example” 。C99已经支持__func__宏,因此建议在Linux编程中 不再使用__FUNCTION__,而转而使用__func__:
void example(void) { printf("This is function:%s", __func__); }
特殊属性声明
- GNU C允许声明函数、变量和类型的特殊属性,以便手动优化代码和定制代码检查的方法。要指定 一个声明的属性,只需要在声明后添加__attribute__((ATTRIBUTE))。其中ATTRIBUTE为属性说 明,如果存在多个属性,则以逗号分隔。
- GNU C支持noreturn、format、section、aligned、packed等十多个 属性
- noreturn属性作用于函数,表示该函数从不返回。这会让编译器优化代码,并消除不必要的警告信 息。例如
# define ATTRIB_NORET __attribute__((noreturn)) .... asmlinkage NORET_TYPE void do_exit(long error_code) ATTRIB_NORET;
- format属性也用于函数,表示该函数使用printf、scanf或strftime风格的参数,指定format属性可以让编 译器根据格式串检查参数类型。例如:
- 代码中的第1个参数是格式串,从第2个参数开始都会根据printf()函数的格式串规则检查参 数
asmlinkage int printk(const char * fmt, ...) __attribute__ ((format (printf, 1, 2)));
- unused属性作用于函数和变量,表示该函数或变量可能不会用到,这个属性可以避免编译器产生警告信息
- aligned属性用于变量、结构体或联合体,指定变量、结构体或联合体的对齐方式,以字节为单位,例如:表示该结构类型的变量以4字节对齐
struct example_struct { char a; int b; long c; } __attribute__((aligned(4)));
- packed属性作用于变量和类型,用于变量或结构体成员时表示使用最小可能的对齐,用于枚举、结构 体或联合体类型时表示该类型使用最小的内存。例如:
struct example_struct { char a; int b; long c __attribute__((packed)); };
内建函数
- GNU C提供了大量内建函数,其中大部分是标准C库函数的GNU C编译器内建版本,例如 memcpy()等,它们与对应的标准C库函数功能相同
- 不属于库函数的其他内建函数的命名通常以__builtin开始,如下所示:
- 内建函数__builtin_return_address(LEVEL)返回当前函数或其调用者的返回地址,参数LEVEL指定 调用栈的级数,如0表示当前函数的返回地址,1表示当前函数的调用者的返回地址
- 内建函数__builtin_constant_p(EXP)用于判断一个值是否为编译时常数,如果参数EXP的值是常 数,函数返回1,否则返回0
- 内建函数__builtin_expect(EXP,C)用于为编译器提供分支预测信息,其返回值是整数表达式EXP 的值,C的值必须是编译时常数
- 例如,下面的代码可检测第1个参数是否为编译时常数以确定采用参数版本还是非参数版本:
#define test_bit(nr,addr) \ (__builtin_constant_p(nr) \ constant_test_bit((nr),(addr)) : \ variable_test_bit((nr),(addr)))
- Linux内核编程时常用的likely()和unlikely()底层调用的likely_notrace()、unlikely_notrace()就 是基于__builtin_expect(EXP,C)实现的
#define likely_notrace(x) __builtin_expect(!!(x), 1) #define unlikely_notrace(x) __builtin_expect(!!(x), 0
- 若代码中出现分支,则即可能中断流水线,我们可以通过likely()和unlikely()暗示分支容易成立还是不容易成立,例如:
if (likely(!IN_DEV_ROUTE_LOCALNET(in_dev))) if (ipv4_is_loopback(saddr)) goto e_inval;
- 在使用gcc编译C程序的时候,如果使用“-ansi–pedantic”编译选项,则会告诉编译器不使用GNU扩展语 法。例如对于如下C程序test.c:
struct var_data { int len; char data[0]; }; struct var_data a; /********************/ 直接编译可以通过: gcc -c test.c 如果使用“-ansi–pedantic”编译选项,编译会报警: gcc -ansi -pedantic -c test.c test.c:3: warning: ISO C forbids zero-size array 'data'
七、do{}while(0)语句
- 在Linux内核中,经常会看到do{}while(0)这样的语句,许多人开始都会疑惑,认为do{}while(0) 毫无意义,因为它只会执行一次,加不加do{}while(0)效果是完全一样的
- 其实do{}while(0)的用法 主要用于宏定义中
演示案例
- 这里用一个简单的宏来演示
#define SAFE_FREE(p) do{ free(p); p = NULL;} while(0)
- 假设这里去掉do...while(0),即定义SAFE_DELETE为
#define SAFE_FREE(p) free(p); p = NULL;
- 那么以下代码:
if(NULL != p) SAFE_DELETE(p) else .../* do something */ 会被展开为: if(NULL != p) free(p); p = NULL; else .../* do something */
- 展开的代码中存在两个问题:
- ①因为if分支后有两个语句,导致else分支没有对应的if,编译失败
- ②假设没有else分支,则SAFE_FREE中的第二个语句无论if测试是否通过,都会执行
- 的确,将SAFE_FREE的定义加上{}就可以解决上述问题了,即:
#define SAFE_FREE(p) { free(p); p = NULL;}
- 这样,代码:
if(NULL != p) SAFE_DELETE(p) else ... /* do something */ 会被展开为: if(NULL != p) { free(p); p = NULL; } else ... /* do something */
- 但是,在C程序中,在每个语句后面加分号是一种约定俗成的习惯,那么,如下代码:
if(NULL != p) SAFE_DELETE(p); else ... /* do something */ 将被扩展为: if(NULL != p) { free(p); p = NULL; }; else ... /* do something */
- 这样,else分支就又没有对应的if了,编译将无法通过。假设用了do{}while(0)语句,情况就不一样 了,同样的代码会被展开为:
if(NULL != p) do{ free(p); p = NULL;} while(0); else ... /* do something */
- 而不会再出现编译问题。do{}while(0)的使用完全是为了保证宏定义的使用者能无编译错误地使用宏, 它不对其使用者做任何假设。
八、goto语句
- 用不用goto一直是一个著名的争议话题,Linux内核源代码中对goto的应用非常广泛,但是一般只限于 错误处理中
- 例如:
if(register_a()!=0)
goto err;
if(register_b()!=0)
goto err1;
if(register_c()!=0)
goto err2;
if(register_d()!=0)
goto err3;
...
err3:
unregister_c();
err2:
unregister_b();
err1:
unregister_a();
err:
return ret;
- 这种将goto用于错误处理的用法实在是简单而高效,只需保证在错误处理时注销、资源释放等,与正 常的注册、资源申请顺序相反