1.1 Linux 内核驱动中的奇怪语法
大家在看一些 GNU 开源软件,或者阅读 Linux 内核、驱动源码时会发现,在 Linux 内核源码中,有大量的 C 程序看起来“怪怪的”。说它是C语言吧,貌似又跟教材中的写法不太一样;说它不是 C 语言呢,但是这些程序确确实实是在一个 C 文件中。此时,你肯定怀疑你看到的是一个“假的 C 语言”!比如,下面的宏定义:
点击(此处)折叠或打开
- #define mult_frac(x, numer, denom)( \
- { \
- typeof(x) quot = (x) / (denom); \
- typeof(x) rem = (x) % (denom); \
- (quot * (numer)) + ((rem * (numer)) / (denom)); \
- } \
- )
- #define ftrace_vprintk(fmt, vargs) \
- do { \
- if (__builtin_constant_p(fmt)) { \
- static const char *trace_printk_fmt __used \
- __attribute__((section("__trace_printk_fmt"))) = \
- __builtin_constant_p(fmt) ? fmt : NULL; \
- \
- __ftrace_vbprintk(_THIS_IP_, trace_printk_fmt, vargs); \
- } else \
- __ftrace_vprintk(_THIS_IP_, fmt, vargs); \
- } while (0)
字符驱动的填充:
点击(此处)折叠或打开
- static const struct file_operations lowpan_control_fops = {
- .open = lowpan_control_open,
- .read = seq_read,
- .write = lowpan_control_write,
- .llseek = seq_lseek,
- .release = single_release,
- };
内核中实现打印功能的宏定义:
点击(此处)折叠或打开
- #define pr_info(fmt, ...) __pr(__pr_info, fmt, ##__VA_ARGS__)
- #define pr_debug(fmt, ...) __pr(__pr_debug, fmt, ##__VA_ARGS__)
本教程,就是带领大家一起去了解 Linux 内核或者 GNU 开源软件中,常用的一些 C 语言特殊语法扩展,扫除阅读 Linux 内核或 GNU 开源软件时,这些扩展特性带给我们的语法阅读障碍和困惑。
1.2 C 语言标准和编译器
什么是 C 语言标准呢?我们生活的现实世界,就是由各种标准构成的,正是这些标准,我们的社会才会有条不紊的运行。比如我们过马路,遵循的交通规则就是一个标准:红灯停,绿灯行,黄灯亮了等一等。当行人和司机都遵循这个默认的标准时,我们的交通系统才会顺畅运行。电脑中的 USB 接口也是一种标准,当大家生产的 USB 产品都遵循 USB 协议这种通信标准时,我们的手机、U 盘、USB 摄像头、USB 网卡才可以在各种电脑设备上互插互拔。2G、3G、4G 也是一种标准,当不同厂家生产的基带芯片都遵循这种通信标准,我们所用的不同品牌、不同操作系统的手机才可能互相打电话、互相发微信、互相给对方点赞。
同样,C 语言也有它自己的标准。我们知道,C 语言程序需要通过编译器,编译生成二进制指令,才能在我们的电脑上运行。在 C 语言刚发布的早期,各大编译器厂商开发自己的编译器时,各自开发,各自维护,时间久了,就会变得比较混乱。这就会造成这样一种局面:程序员写的程序,在一个编译器上编译通过,在另一个编译器编译通不过。大家按各自的习惯来,谁也不服谁,就像春秋战国时代:不同的货币、不同的度量衡,不同的文字,都是中国人,因为标准不统一,所以交流起来很麻烦,这样下去也不是办法啊。
后来 ANSI(AMERICAN NATIONAL STANDARDS INSTITUTE: 美国国家标准协会,简称 ANSI)出山了,联合 ISO(国际化标准组织)召集各个编译器厂商大佬,各种技术团体,一起喝个茶、开个碰头会,开始启动 C 语言的标准化工作。期间各种大佬之间也是矛盾重重,充满各种争议,但功夫不负有心人,经过艰难的磋商,终于在1989年达成一致,发布了 C 语言标准,后来第二年又做了一些改进。于是,就像秦始皇统一六国、统一文字和度量衡一样,C 语言标准终于问世了!因为是在 1989 年发布的,所以人们一般称其为 C89 或 C90 标准,或者叫做 ANSI C。
1.3 C 标准内容
C 标准英文文档,洋洋洒洒几百页,讲了很多东西,但总体归纳起来,主要就是 C 语言编程的一些语法惯例,比如:
- 定义各种关键字、数据类型
- 定义各种运算规则
- 各种运算符的优先级和结合性
- 数据类型转换
- 变量的作用域
- 函数原型
- 函数嵌套层数
- 函数参数个数限制
- 标准库函数
1.4 C 标准的发展过程
- K&R C
- ANSI C
- C99
- C11
- K&R C
ANSI C
- 增加 signed、volatile、const 关键字
- 增加 void* 数据类型
- 增加预处理器命令
- 增加宽字符、宽字符串
- 定义了 C 标准库
- ……
C99 标准
- 布尔型:_Bool
- 复数:_Complex
- 虚数:_Imaginary
- 内联:inline
- 指针修饰符:restrict
- 支持long long、long double数据类型
- 支持变长数组
- 允许对结构体特定成员赋值
- 支持16进制浮点数、float _Complex等数据类型
- ……
变量声明可以放代码块的任何地方。ANSI C 规定变量的声明要全部写在函数语句的最前面,否则就会报编译错误。现在不需要这样写了,哪里需要使用变量,在哪里直接声明使用即可;
源程序每行最大支持4095个字节。这个貌似足够用了,没有什么程序能复杂到一行程序有4KB个字符;
支持//单行注释。ANSI C使用/**/没有C++的//注释方便,所以 C99 新标准借鉴过来了,也开始支持这种注释方式;
标准库新增了一些头文件:如 stdbool.h、complex.h、stdarg.h、fenv.h 等。大家在 C 语言中经常返回的 true、false,其实这也是 C++ 里面定义的 bool 类型。那为什么我们经常这样写,而编器编译程序时没有报错呢,这是因为早期大家编程使用的都是 VC++6.0 系列,是 C++ 编译器。还有一种可能就是有些 IDE 对这个数据类型的数据做了封装。
C11 新标准
- 增加 _Noreturn,声明函数无返回值;
- 增加_Generic:支持泛型编程;
- 修改了标准库函数的一些 Bug:如 gets( )函数被 gets_s() 函数代替;
- 新增文件锁功能;
- 支持多线程;
- ……
1.5 编译器对 C 标准的支持
现在 5G 标准正在研发,快发布了,据说 2019 年发布,2020 年商用。但是目前还没有手机支持 5G 通信,就跟现在没有编译器支持 C11 标准一样。
不同编译器,甚至对 C 标准的支持也不一样。有的编译器只支持 ANSI C,这是目前默认的 C 标准。有的编译器可以支持 C99,或者支持 C99 标准的部分特性。目前对 C99 标准支持最好的是 GNU C 编译器,据说可以支持 C99标准99%的新增特性。
1.6 编译器对 C 标准的扩展
在51单片机上用 C 语言开发程序,我们经常使用 Keil for C51 集成开发环境。你会发现 Keil for C51 或其他 IDE 里的 C 编译器会对 C 语言标准作很多扩展。比如增加各种关键字:
- data:RAM 的低128B空间,单周期直接寻址;
- code:表示程序存储区;
- bit:位变量,常用来定义单片机的 P0~P3 管脚;
- sbit:特殊功能位变量;
- sfr:特殊功能寄存器;
- reentrant:重入函数声明。
同样的道理,GCC 编译器,也对 C 标准做了很多扩展:
- 零长度数组
- 语句表达式
- 内建函数
- __attribute__特殊属性声明
- 标号元素
- case 范围
- ...
点击(此处)折叠或打开
- int a[0];
1.7 本教程主要内容
1.8 本教程需要的学习环境
- Linux学习环境:Ubuntu、Fedora等皆可;
- arm-linux-gnueabi-gcc 交叉编译工具;
- Linux 内核源码:Linux 4.4.x
- U-boot-2016.09 源代码
- 备注
微信公众号:宅学部落(armlinuxfun)
嵌入式在线学习咨询群:475504428
更多嵌入式视频教程:https://wanglitao.taobao.com
本教程电子书籍下载地址:https://pan.baidu.com/s/1a6L0cyIQKKLlmIfRw7U6Dg