谈起linux的编程,我们首先就会想到GNU。GNU计划,又称革奴计划,是由Richard Stallman在1983年9月27日公开发起的。它的目标是创建一套完全自由的操作系统。它在编写linux的时候自己制作了一个标准成为 GNUC标准。ANSI,全称为美国国家标准协会,它对C做的标准后来被国际标准协会接收成为标准C ,所以 ANSI C即标准C。 GNU C对标准C进行了一系列扩展,以增强标准C的功能。熟悉这些扩展的功能,对linux的编程及代码阅读大有好处。它相对标准C有以下特点: 一:零长度数组 GNU C 允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。例如: - struct var _ data
- {
- int len;
- char data[0];
- };
复制代码char data[0]仅仅意味着程序中通过 var_data 结构体实例的 data[index]成员可以访问 len 之后的第 index 个地址,它并没有为 data[]数组分配内存,因此 sizeof(structvar_data)=sizeof(int)。 假设 structvar_data 的数据域保存在 structvar_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': 三:语句表达式 GNUC 把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可 以出现在任何允许表达式的地方。我们可以在语句表达式中使用原本只能在复合语句 中使用的循环变量、局部变量等,例如: - #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 这两个局部变量,所以以上述方式定义的宏将不会有副作用。 四:typeof关键字 typeof的参数可以是两种形式:表达式或类型。 表达式的示例如下: - extern int foo();
- typeof(foo()) var;
复制代码以上示例声明了int类型的var变量,因为表达式foo()是int类型的。由于表达式不会被执行,所以不会调用foo函数。 类型的示例如下: 以上语句等价于: 注意,使用typeof构造的类型名不能包含存储类说明符,如extern或static。不过允许包含类型限定符,如const或volatile。例如,下列代码是无效的,因为它在typeof构造中声明了extern: typeof构造的主要应用是用在宏定义中。可以使用typeof关键字来引用宏参数的类型。因此,在没有将类型名明确指定为宏实参的情况下,构造带有所需类型的对象是可能的。
下面是一个交换两个变量的值的宏定义: - #define SWAP(a,b) {\
- typeof(a) _t=a;\
- a=b;\
- b=_t;}
复制代码这个宏可以交换所有基本数据类型的变量,如整数,字符,结构等。 五:可变参数的宏 标准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预处理器会丢弃前面的逗号,这时 会被正确地扩展为: 而不是: 六:标号元素 标准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,
- .ioctl = 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”。 八:特殊属性声明 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)));
复制代码上述代码中的第一个参数是格式串,从第二个参数开始都会根据 printf()函数的格式串规则检查参数。 unused 属性作用于函数和变量,表示该函数或变量可能不会被用到,这个属性可以避免编译器产生警告信息。 aligned 属性用于变量、结构体或联合体,指定变量、结构体或联合体的对界方式,以字节为单位,例如: - struct example _ struct
- {
- char a;
- int b;
- long c;
- } _ _ attribute _ _ ((aligned(4)));
复制代码表示该结构类型的变量以4字节对界。 packed属性作用于变量和类型,用于变量或结构体成员时表示使用最小可能的对界,用于枚举、结构体或联合体类型时表示该类型使用最小的内存。例如: - struct example _ struct
- {
- char a;
- int b;
- long c _ _ attribute _ _ ((packed));
- };
复制代码 八:内建函数 GNUC 提供了大量的内建函数,其中大部分是标准 C 库函数的 GNU C 编译器内建版本,例如 memcpy()等,它们与对应的标准 C 库函数功能相同。不属于库函数的其他内建函数的命名通常以__builtin 开始,如下所示。 l 内建函数_ _builtin_return_address(LEVEL)返回当前函数或其调用者的返回地址,参数 LEVEL 指定调用栈的级数,如 0 表示当前函数的返回地址,1 表示当前函数的调用者的返回地址。 l 内建函数_ _builtin_constant_p(EXP)用于判断一个值是否为编译时常数, 如果参数 EXP 的值是常数,函数返回 1,否则返回 0。 l 内建函数_ _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)))
复制代码 |