GCC 语法扩展
使用VC++6.0编译下面一段C语言程序:
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,
};
你会发现,编译会报语法错误。同样的一段代码,如果使用GCC编译器编译,则可以正常编译。
这是因为,不同的编译器,对C语言标准版本的支持情况不一样。
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。
C 标准并不是永远不变的,就跟移动通信一样,也是从 2G、3G、4G 到 5G 不断发展变化的。C 标准也经历了下面四个阶段:
- K&R C
- ANSI C
- C99
- C11
K&R C
K&R C 一般也称为传统 C。在 C 标准没有统一之前,C 语言的作者 Dennis Ritchie 和 Brian Kernighan 合作写了一本书《C 程序设计语言》。早期程序员编程,这本书可以说是绝对权威。这本书很薄,内容精炼,主要介绍了 C 语言的基本使用方法。后来《C 程序设计语言》第二版问世,做了一些修改:比如新增 unsigned int、long int、struct 等数据类型;把运算符 =+/=- 修改为 +=/-=,避免运算符带来的一些歧义和 Bug。这本书可以看作是 ANSI 标准的雏形。但早期的 C 语言还是很简单的,比如还没有定义标准库函数、没有预处理命令等。
ANSI C
ANSI C 是 ANSI(美国国家标准协会)在 K&R C 的基础上,统一了各大编译器厂商的不同标准,并对 C 语言语法和特性做了一些扩展,而发布的一个标准。这个标准一般也叫做 C89/C90,也是目前各种编译器默认支持的 C 语言标准。ANSI C 主要新增了以下特性:
- 增加 signed、volatile、const 关键字
- 增加 void* 数据类型
- 增加预处理器命令
- 增加宽字符、宽字符串
- 定义了 C 标准库
- ……
C99 标准
C99 标准是 ANSI 1999 年在 C89 标准的基础上新发布的一个标准,该标准对 ANSI C 标准做了一些扩充,比如新增一些关键字,支持新的数据类型:
- 布尔型:_Bool
- 复数:_Complex
- 虚数:_Imaginary
- 内联:inline
- 指针修饰符:restrict
- 支持long long、long double数据类型
- 支持变长数组
- 允许对结构体特定成员赋值
- 支持16进制浮点数、float _Complex等数据类型
- ……
除此之外,C99 标准也借鉴其它语言的一些优点,对语法和函数做了一系列改进,大大方便了程序员开发程序,比如:
C11 新标准
C11 标准是2011年发布的最新 C 语言标准,修改了 C 语言标准的一些 Bug、新增了一些特性:
- 增加 _Noreturn,声明函数无返回值;
- 增加_Generic:支持泛型编程;
- 修改了标准库函数的一些 Bug:如 gets( )函数被 gets_s() 函数代替;
- 新增文件锁功能;
- 支持多线程;
- ……
从 C11 标准的修改内容来看,也慢慢察觉到 C 语言未来的发展趋势:C 语言现在也在借鉴现在编程语言的优点,不断添加到自己的标准里面。比如现代编程语言的多线程、字符串、泛型编程等,C 语言最新的标准都支持。但是这样下去,C 语言是不是还能保持她“简单就是美”的优雅特色呢,我们只能慢慢期待了。但至少目前我们不用担心这些,因为 C11 新发布的标准,目前绝大多数编译器还不支持,所以我们暂时还用不到。
编译器对 C 标准的支持
标准是一回事,各种编译器支不支持是另一回事,这一点,大家要搞清楚。这就跟手机一样,不同时期发布的手机对通信标准支持也不一样。早期的手机可能只支持 2G 通信,后来支持 3G,现在发布的新款手机基本上都支持 4G了,而且可以兼容 2G/3G。
现在 5G 标准正在研发,快发布了,据说 2019 年发布,2020 年商用。但是目前还没有手机支持 5G 通信,就跟现在没有编译器支持 C11 标准一样。
不同编译器,甚至对 C 标准的支持也不一样。有的编译器只支持 ANSI C,这是目前默认的 C 标准。有的编译器可以支持 C99,或者支持 C99 标准的部分特性。目前对 C99 标准支持最好的是 GNU C 编译器,据说可以支持 C99标准99%的新增特性。
不同版本的GCC编译器,对C标准的支持也不一样。
C标准 | ||||||
---|---|---|---|---|---|---|
GCC 版本 | C89/C90 | C99 | C11 | GNU90 | GNU99 | GNU11 |
10.1 ~ 8.4 | c89 / c90 | c99 | c11 | gnu90/gnu89 | gnu99 | gnu11 |
7.5 ~ 5.5 | c89/c90 | c99 | c11 | gnu90/gnu89 | gnu99 | gnu11 |
4.9.4 ~ 4.8.5 | c89/c90 | c99 | c11 | gnu90/gnu89 | gnu99 | gnu11 |
4.7.4 | c89/c90 | c99(部分支持) | c11(部分支持) | gnu90/gnu89 | gnu99(部分支持) | gnu11(部分支持) |
4.6.4 | c89/c90 | c99(部分支持) | c1x(部分支持) | gnu90/gnu89 | gnu99(部分支持) | gnu1x(部分支持) |
4.5.4 | c89/c90 | c99(部分支持) | gnu90/gnu89 | gnu99(部分支持) |
编译器对 C 标准的扩展
不同编译器,出于开发环境、硬件平台、性能优化的需要,除了支持 C 标准外,还会自己做一些扩展。
在51单片机上用 C 语言开发程序,我们经常使用 Keil for C51 集成开发环境。你会发现 Keil for C51 或其他 IDE 里的 C 编译器会对 C 语言标准作很多扩展。比如增加各种关键字:
- data:RAM 的低128B空间,单周期直接寻址;
- code:表示程序存储区;
- bit:位变量,常用来定义单片机的 P0~P3 管脚;
- sbit:特殊功能位变量;
- sfr:特殊功能寄存器;
- reentrant:重入函数声明。
如果你在程序中使用以上这些关键字,那么你的程序就只能使用51编译器来编译运行,你使用其它的编译器,比如 VC++6.0,是编译通不过的。
同样的道理,GCC 编译器,也对 C 标准做了很多扩展:
- 零长度数组
- 语句表达式
- 内建函数
- __attribute__特殊属性声明
- 标号元素
- case 范围
- …
比如支持零长度数组。这些新增的特性,C 标准目前是不支持的,其它编译器也不支持。如果你在程序中定义一个零长度数组:
int a[0];
只能使用 GCC 编译器才能正确编译,使用 VC++ 6.0编译器编译可能就通不过,因为微软的 C++ 编译器不支持这个特性。
关于GCC编译器的更多扩展语法,请移步:嵌入式C语言自我修养 :GNU C编译器扩展语法精讲