1.ANSI/ISO C
ANSI C是C语言的标准,任何C语言的编译器都在ANSI C的基础上扩充。
1990年,国际化标准组织ISO(Intrernational StandardOrganization)接受了ANSI C(C89)为ISO C的标准(ISO9899-1990)。1994年,ISO修订了C语言的标准。目前流行的C语言编译系统大多是以ANSI C为基础进行开发的,但不同版本的C编译系统所实现的语言功能和语法规则有略有差别。
在ANSI标准化后,C语言的标准在一段相当的时间内都保持不变,尽管C++继续在改进。(实际上,Normative Amendment1在1995年已经开发了一个新的C语言版本。但是这个版本很少为人所知。)标准在90年代才经历了改进,这就是ISO9899:1999(1999年出版)。这个版本就是通常提及的C99。它被ANSI于2000年三月采用。
2.GNU C
gcc(GNU CC)是一个功能非常强大的跨平台C编译器。
它对标准C(ANSI/ISO C)进行了很多扩展,这些扩展对优化目标代码布局安全性的检测方面提供很强的支持,我们把支持GNU扩展的C语言称为GNU C。
Linux内核代码使用了大量的GNU C扩展,以至于唯一能够编译Linux内核的编译器就是gcc。下面是相对于标准C,GNU C扩展的一些特殊之处:
1.允许零长度数组
GNU C允许零长度数组,在定义变长对象的头结构时,这个特性非常有用。
struct var_data s
{
int len;
char data[0];
};
char data[0]仅仅意味着程序中通过var_data的结构体实例的data[index]成员可以访问len之后的第index个地址,并没有为data[0]分配内存。
假设struct var_data的数据域保存在struct var_data紧接着的内存区域,通过如下代码可以遍历这些数据:
struct var_data s;
...
for (i=0;i
{
printf("%02x",s.data[i]);
}
2、case范围
GNU C 支持case x...y 这样的语法,区间[x,y]的数都会满足这个case的条件.
switch(c)
{
case '0'...'9': c-='0';
break;
case 'a'...'f': c-='a'-10;
break;
case 'A'...'F': c-='A'-10;
break;
}
这个case的特点大家都看得出来,比标准C少敲了多少case啊
3、语句表达式
GNU C把包含在括号里的复合语句看做是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方。我们可以在语句表达式中使用原本只能在复合语句中使用的循环变量、局部变量等,例如
#define min_t(type,x,y) /
({type __x=(x); type __y=(y);__x<__y>
int ia,ib,mini;
mini=min_t(int,ia,ib);
这样,因为重新定义了__x和__y这两个局部变量,所以上述方法定义的宏将不会有副作用。在标准C中,对应的宏通常会有副作用:
#define min(x,y) ((x)
而代码min(++ia,++ib)将会被展开为
((++ia)
这个在 嵌入式程序员应知道的0x10个基本问题 里有讲过。
4、typeof关键字
typeof(x)语句可以获得x的类型,因此,我们可以借助typeof重新定义第3条提到的min_t这个宏
#define min(x,y) /
({ /
const typeof(x) _x=(x);/
const typeof(y) _y=(y);/
(void) (&_x==&_y);/
_x<_y _x: _y>
我们不需要像第三条时那样传一个type进去,因为通过typeof(x)可以得到type。
代码 (void) (&_x==&_y);的作用是检查_x和_y的类型是否一致。
5、可变参数的宏
标准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",);
6.标号元素
标准c要求数组或结构体的初始化值必须以固定的顺序出现,在GNU C中,通过指定索引或结构体成员名,允许初始化值得以任意顺序出现。
指定数组索引的方法是在初始化值前添加 [INDEX]= ,当然也可以用 [FIRST...LAST]= 的形式指定一个范围。例如下面的代码定义一个数组,并把其中的所有元素赋值为0:
unsigned char data[MAX] ={[0...MAX-1]=0 };
下面的代码借助结构体成员名初始化结构体:
struct file_operations DEMO_fops = {
owner : THIS_MODULE,
llseek: DEMO_llseek,
read: DEMO_read,
write: DEMO_write,
ioctl: DEMO_ioctl,
open: DEMO_open,
release: DEMO_release,
};
但是Linux 2.6还是推荐采用标准C的方式,如下
struct file_operations DEMO_fops = {
.owner = THIS_MODULE,
.llseek = DEMO_llseek,
.read = DEMO_read,
.write = DEMO_write,
.ioctl = DEMO_ioctl,
.open = DEMO_open,
.release = DEMO_release,
};
7.当前函数名
GUN C预定义了两个标识符保存当前的函数名,__FUNCTION__保存函数在源码中的名字,
__PRETTY_FUNCTION__保存带语言特色的名字。在c函数中,这两个名字是相同的。
void example()
{
printf("This is function: %s ",__FUNCTION__);
}
代码中的__FUNCTION__意味着字符串"example"
8、特殊属性声明
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属性可以让编译器根据格式串检查参数类型。例如:
asmlinkage int printk(const char * fmt,...)/
__attribute__((format(printf,1,2)));
unused属性作用于函数和变量,表示该函数或变量可能不会被用到,避免编译器产生的警告信息。
aligned属性指定结构体、变量、联合体的对齐方式。packed属性作用于变量和类型,表示压缩结构体,使用最小的内存。
struct examprl_struct
{
char a;
int b;
long c;
}__attribute__((packed));
注意,这个__attribute__((packed))只能用在GNU C
9、内建函数
GNU C 提供了大量的内建函数,其中很多是标准 C 库函数的内建版本,例如memcpy,它们与对应的 C 库函数功能相同,本文不讨论这类函数,其他内建函数的名字通常以 __builtin 开始。
* __builtin_return_address (LEVEL)
内建函数 __builtin_return_address 返回当前函数或其调用者的返回地址,参数LEVEL 指定在栈上搜索框架的个数,0 表示当前函数的返回地址,1 表示当前函数的调用者的返回地址,依此类推。例如:
++++ kernel/sched.c
437: printk(KERN_ERR “schedule_timeout: wrong timeout ”
438: “value %lx from %p/n”, timeout,
439: __builtin_return_address(0));
* __builtin_constant_p(EXP)
内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数 EXP 的值是常数,函数返回 1,否则返回 0。例如:
++++ include/asm-i386/bitops.h
249: #define test_bit(nr,addr) /
250: (__builtin_constant_p(nr) ? /
251: constant_test_bit((nr),(addr)) : /
252: variable_test_bit((nr),(addr)))
很多计算或操作在参数为常数时有更优化的实现,在 GNU C 中用上面的方法可以根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在参数是常数时编译出最优化的代码。
* __builtin_expect(EXP, C)
内建函数 __builtin_expect 用于为编译器提供分支预测信息,其返回值是整数表达式 EXP 的值,C 的值必须是编译时常数。例如:
++++ include/linux/compiler.h
13: #define likely(x) __builtin_expect((x),1)
14: #define unlikely(x) __builtin_expect((x),0)
++++ kernel/sched.c
564: if (unlikely(in_interrupt())) {
565: printk(”Scheduling in interrupt/n”);
566: BUG();
567: }
这个内建函数的语义是 EXP 的预期值是 C,编译器可以根据这个信息适当地重排语句块的顺序,使程序在预期的情况下有更高的执行效率。上面的例子表示处于中断上下文是很少发生的,第 565-566 行的目标码可能会放在较远的位置,以保证经常执行的目标码更紧凑。