Linux操作系统是由C语言编写的,也许读者对C语言编程已经比较熟悉了,但是我们既然要进入Linux开发,那么了解在Linux下C语言编程的特点则是必不可少的一步。
本篇文章将从三个小节对其进行介绍,分别为:
1、Linux的编码风格;
2、GNU C;
3、do{} while(0)语句。
1、Linux的编码风格
Linux程序的命名习惯和Windows程序的命名习惯有很大的不同。在Windows程序中,习惯用如下的方式命名宏、变量和函数:
这种命名方式在程序开发中非常流行,意思表达清晰,单词之间通过首字母大写来区分。通过第一个单词的首字母是否大写可以区分名称属于变量还是属于函数,而看到一串大写字母则可以断定为宏。实际上这种命名习惯并非仅限于Windows编程,许多领域的程序开发都遵照这种习惯(我之前在很长一段时间里也都是按照这种编程风格进行开发)。
但是Linux不以这种习惯命名,对于上图中的代码,在Linux中会被这样命名:
在上述的命名方式中,下划线大行其道,不再按照Windows所采用的用首字母大写来区分单词的方式。Linux的命名风格和Windows命名风格不存在孰优孰劣,但是既然我们是立足于Linux开发,那么我建议代码风格应该与Linux保持一致。另外,Linux中的代码缩进使用"TAB",而不是空格。
Linux中代码括号“{”和“}”的使用原则如下:
- 对于结构体、if/for/while/switch语句,“}”不另起一行,例如:
- 如果if、for语句只有一行,不要加“{”和“}”,例如:
应该修改为:
- if和else使用的时候,else语句不另起一行,例如:
- 对于函数,“{”另起一行,例如:
2、GNU C
Linux上可用的C编译器是GNU C编译器,GNU C对标准C进行了一系列扩展,用以增强标准C的功能。
2.1 case范围
GNU C支持case x...y这样的语法,区间[x,y]中的数都会满足这个case的条件,请看下面的代码:
代码中的case'0'...'9'等价于标准C中的:
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '9': case '9':
2.2 语句表达式
GNU C把包含在括号中的复合语句看成是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方。例如:
因为重新定义了_x和_y这两个局部变量,所以使用这样定义的宏将不会有副作用。在标准C中,对应的宏会产生副作用
代码min(++a,++b)会展开为((++a)
2.3 可变参数宏
标准C就支持了可变参数函数,意味着函数的参数是不固定的,例如我们最熟悉的printf()函数的原型为:
而在GNU C,宏也可以接受可变数目的参数,例如:
这里的arg表示其余的参数,可以有零个或多个参数,这些参数以及参数之间的逗号构成arg的值,在宏扩展的时候替换arg,如下列代码:
会被扩展为:
使用“##”是为了处理arg不代表任何参数的情况,这时候,前面的逗号就变得多余,使用##“”之后,GNU C预处理器会丢弃掉前面的逗号,这样的话以下代码:
会被正确的替换为:
而不是:
2.4 标号元素
标准C要求数组或结构体的初始化值必须以固定的顺序出现,在GNU C中,通过指定的索引或结构体成员名,允许初始化值以任意顺序出现。指定数组索引的方法是在初始化值前添加"[INDEX]=",当然也可以用"[FIRST...LAST]="的形式指定一个范围。例如下面的代码定义了一个数组,并且把其中的所有元素赋值为0:
下面的代码借助结构体的成员名初始化结构体:
但是,Linux2.6推荐类似的代码应该尽量采用标准C的方式:
2.5 当前的函数名
GNU C预定义了一个标识符保存当前函数的名字,__FUNCTION__保存函数在源码中的名字。
代码中的__FUNCTION__其实就是意味着函数名“example”。目前C99已经支持__func__宏,所以建议在Linux编程中不再使用__FUNCTION__,而转而使用__func__。
3、do{} while(0)语句
在Linux内核中,经常会看到do{} while(0)这样的语句,许多人开始都会很疑惑,认为do{} while(0)毫无意义,因为它只会执行一次,加不加do{} while(0)的效果是完全一样的,其实do{} while(0)的语法主要用于宏定义中。这里用一个简单的宏来说明:
如果这里去掉do...while(0),即定义为
那么以下代码:
会被展开为:
展开的这个代码中存在两个问题:(1)因为if分支后有两个语句,导致else分支没有对应的if,编译会失败;(2)假设没有else分支,则第二个语句无论if条件测试是否通过都会执行。这个问题也可以避免,只要我们在宏定义中加上大括号如下所示:
的确,将它的定义加上“{ }”就可以解决上述存在的问题了,但是,在C程序中,在每个语句后面加分号是一种默认的习惯,那么,将会被扩展为:
这样,无论if条件测试是否通过都会执行后面分号带来的空语句,所以else分支就又没有对应的if了,编译还是会无法通过。如果用了do{} while(0)语句,情况就不一样了,这个代码会会扩展成:
这样不会再出现编译的问题,do{} while(0)的使用完全是为了保证宏定义的使用者可以无编译错误的使用宏,它不对使用者做出任何假设。
本篇文章主要介绍了Linux内核编程的基础知识,为读者在进行Linux设备驱动开发打下软件基础。由于Linux驱动编程的本质属于内核编程,因此掌握内核编程的基础知识显得尤为重要。
我会定期更新嵌入式Linux开发和STM32开发的编程知识和经验总结。欢迎关注,也欢迎在评论区留言