Chinaunix首页 | 论坛 | 博客
  • 博客访问: 16080
  • 博文数量: 1
  • 博客积分: 25
  • 博客等级: 民兵
  • 技术积分: 12
  • 用 户 组: 普通用户
  • 注册时间: 2010-11-12 19:04
文章分类

全部博文(1)

文章存档

2018年(1)

我的朋友
最近访客

分类: C/C++

2018-11-13 17:00:57

原文地址:GNU C的扩展 作者:王贤才

GNU C的扩展

一.  结构体赋值:

1、对成员赋值

例如结构体

[cpp] view plain copy
  1. struct  st1 {  
  2. int a;  
  3. int b;  
  4. int c;  
  5. }  


1.1 用 {}形式

[cpp] view plain copy
  1. struct st1 st1 = {1,2,3);  

1.2   kernel风格

[cpp] view plain copy
  1. struct st1 st1 = {  
  2. .a = 1,  
  3. .b = 2,  
  4. .c =3,  
  5. };  

注:此风格(即在成员变量之前加点“.”),可以不按成员变量的顺序进行赋值。如可以为

[cpp] view plain copy
  1. struct st1 st1 = {  
  2. .c = 3,  
  3. .a = 1,  
  4. .b = 2,  
  5. };  

2.对整体赋值.

[cpp] view plain copy
  1. struct st1 a, b;  
  2. b = a;  

3.结构体作为函数返回值对另一个结构体赋值.

[cpp] view plain copy
  1. struct st1 func1();  
  2. struct st1 a = func1();  

二内联函数 inline

在c 中,为了解决一些频繁调用的小函数而大量消耗栈空间或者是叫栈内存的问题,特别的引入了inline修饰符,表示为内联函数。内联函数使用inline关键字定义,并且函数体和声明必须结合在一起,否则编译器将他作为普通函数对待inline函数一般放在头文件中

[cpp] view plain copy
  1. inline void function(int x); //仅仅是声明函数,没有任何效果  
  2. inline void function(int x) //正确  
  3. {  
  4. return x;  
  5. }  

三.typeof用法

1.关键字typeof用于获取表达式的数据类型

(1)char*chptr;

[cpp] view plain copy
  1. typeof (*chptr) ch;    //等价于char ch  
  2. typeof  (ch) *chptr1;  //等价于char *chptr1  
  3. typeof (chptr1) array[5]; //char  *array[5]  
  4. chptr1的数据类型为char *  

(2)typeof常用在linux内核中

[cpp] view plain copy
  1. #define  min(x,y)  ({                   \  
  2.      typeof(x)  __min1 = (x);        \  
  3.      typeof(y) __min2 = (y);             \  
  4.      (void)  (& __min1 == & __min2);     \  
  5.     __min1 < __min2  ? __min1 :min2})  

通过typeof获得x和y的数据,然后定义两个临时变量,并把x和y的分别赋给两个临时变量最后进行比较。另外,宏定义中(void)(& __min1 = &__min2)语句的作用是用来警告xy不能属于不同的数据类型。

实例:

[cpp] view plain copy
  1. #include   
  2. #define  min(x,y)  ({                \  
  3.      typeof(x)  __min1 = (x); \  
  4.      typeof(y) __min2 = (y);  \  
  5.      (void)  (& __min1 == & __min2); \  
  6.      __min1 < __min2  ? __min1 :min2})  
  7. int main()  
  8. {  
  9.     int a=4;  
  10.     int b=6;  
  11.      int min_data;  
  12.      min_data=min(a,b);  
  13.      printf(“the min=%d\n”,min_data);  
  14.      return 0;  
  15. }  

执行结果:

the min=4

如改为:

[cpp] view plain copy
  1. #include   
  2. #define  min(x,y)  ({                \  
  3.      typeof(x)  __min1 = (x); \  
  4.      typeof(y) __min2 = (y);  \  
  5.      (void)  (& __min1 == & __min2); \  
  6.      __min1 < __min2  ? __min1 :min2})  
  7. int main()  
  8. {  
  9.      int a=4;  
  10.      float b=6;  
  11.      int min_data;  
  12.      min_data=min(a,b);  
  13.      printf(“the min=%d\n”,min_data);  
  14.      return 0;  
  15. }  

则会提示以下警告:

main.c: In function ‘main’:

main.c:17:9: warning: comparison of distinct pointer types lacks acast [enabled by default]

四.GNU C之表达式中的复合语句

1.前言:

标准C中,表达式指的是运算符和操作数的组合,而复合语句指的是由一个或多个被括在花括号里的语句构成的代码块。在标准C中,不允许将复合语句用于表达式中

在GNU C中,允许用小括号括起来的复合语句出现在一个表达式中。这个表达式的类型为复合语句中以分号结尾的最后一个子语句表达式的类型其值也为最后子表达式的值

 实例:

[cpp] view plain copy
  1. #include   
  2.    main()  
  3.    {  
  4.            int  a = ({  
  5.                       int b =4;  
  6.                       int c =3;  
  7.                       b+c;  
  8.                       b+c-2;  
  9.                    });  
  10.           printf("a = %d\n",a);  
  11.           return 0;  
  12.   }  

输出结果为:

jibo@jibo-VirtualBox:~/cv_work/work/GNUC/brace$ ./main

a = 5

说明:

a的数值是复合语句中最后一个语句的值,并且它的数据类型与最后一个语句的数据类型相匹配。

2.这种特性常用于在Linux内核中常被用于宏的定义中

[cpp] view plain copy
  1. #define  min(x,y)  ({                \  
  2.      typeof(x)  __min1 = (x); \  
  3.      typeof(y) __min2 = (y);  \  
  4.      (void)  (& __min1 == & __min2); \  
  5.      __min1 < __min2  ? __min1 :min2})  

此处定义了一个安全的求最小值的宏,在标准C中,通常定义为:

[cpp] view plain copy
  1. #define min(x,y) ((x) < (y) ? (x) : (y))  

这个定义计算 x 和 y 分别两次,当参数有副作用时,将产生不正确的结果,使用语句表达式只计算参数一次,避免了可能的错误。语句表达式通常用于宏定义。

五.GNU C之标号元素

标准 C 要求数组或结构变量的初使化值必须以固定的顺序出现,在 GNU C 中,通过指定索引或结构域名,允许初始化值以任意顺序出现。指定数组索引的方法是在初始化值前写 '[INDEX] =',要指定一个范围使用 '[FIRST ... LAST] =' 的形式。

1.在数组中的应用

在数组的初始化列表中使用“[index]=value”这样的形式即可以实现对指定(通过index指定)的某个元素进行初始化。

实例

[cpp] view plain copy
  1. #include   
  2. int main(void)  
  3.   {  
  4.           int i;  
  5.           int arr[6] = {[3] =10,11,[0]=5,6};  
  6.    
  7.           for (i=0;i<6;i++)  
  8.                  printf("a[%d]=%d\n",i,arr[i]);  
  9.    
  10.          return 0;  
  11. }  

执行结果为:

jibo@jibo-VirtualBox:~/cv_work/work/GNUC/initializer $ ./main

a[0]=5

a[1]=6

a[2]=0

a[3]=10

a[4]=11

a[5]=0

说明:

如果在一个指定初始化项目后跟有不至一个值,如[3]=10,11。则这些多余的数值将用来对后续的数组元素进行初始化,即数值11用来初始化arr[4].

对数组来说,在初始化一个或多个元素后,其中未经初始化的元素将被自动地初始化为0后者NULL(针对指针变量而言)。未经过任何初始化的数组,其所有元素的值都将是不确定的。

2.GNU C还支持”[first…last]=value”的形式,即以个范围内的几个元素被初始化为同一值。

实例

[cpp] view plain copy
  1. #include   
  2.  int main()  
  3.  {  
  4.          int i;  
  5.          int arr[]={ [0 ... 3] =1,[4 ... 5]=2,[6 ... 9] =3};  
  6.          for(i=0; i<sizeof(arr)/sizeof(arr[0]);i++ )  
  7.                  printf("arr[%d]:%d\n",i,arr[i]);  
  8.           return 0;  
  9.  }  

执行结果为:

jibo@jibo-VirtualBox:~/cv_work/work/GNUC/initializer $ ./main1

arr[0]:1

arr[1]:1

arr[2]:1

arr[3]:1

arr[4]:2

arr[5]:2

arr[6]:3

arr[7]:3

arr[8]:3

arr[9]:3

六.GNU C之匿名联合或结构体

在GNU C中,可以在结构体中声明某个联合体(或结构体)而不用指出它的名字,这样之后,就可以像使用结构体成员一样直接使用其中联合体(或结构体)的成员一样直接使用其中联合体(或结构体)的成员。

实例:


[cpp] view plain copy
  1. #include   
  2.       struct test_struct {  
  3.             char * name;  
  4.             union {  
  5.                   char gender;  
  6.                   int id;  
  7.            };  
  8.            int num;  
  9.   };  
  10.  int main(void)  
  11.  {  
  12.           struct test_struct test_struct={"jibo",'F',28};  
  13.     
  14.           printf("test_struct.gender=%c,test_struct.id=%d\n",test_struct.gende,  
  15. test_sturct.id)  
  16.           return 0;  
  17.   }  


执行结果为:

jibo@jibo-VirtualBox:~/cv_work/work/GNUC/non_struct $./main

test_struct.gender=F,test_struct.id=70

注:在linux内核中常用匿名联合(或结构体)。

七.GNU C之分支声明:

对于条件选择语句,gcc内建了一条指令用于优化,在该条件经常出现,或者该条件很少出现的时候,编译器可以根据这条指令对条件分支选择进行优化。内核把这条指令封装成了宏,即likely()和unlikely()

例如:


[cpp] view plain copy
  1. if (foo){  
  2.     /**/  
  3. }  


如果想要把这个选择标记成绝少发生的分支:

[cpp] view plain copy
  1. if (unlikely(foo)){  
  2.     /**/  
  3. }  

相反,如果想把一个分支标记为通常为真的选择:

[cpp] view plain copy
  1. if(likely(foo)) {  
  2.     /**/  
  3. }  

注:关于likely()和unlikely()的具体分析见Linux 内核源码中likely()和unlikely()

八.零长度的数组

在标准C中长度为零的数组是被禁止的,其要求数组最少长度为1个字节。但是GCC允许定义零长数组。那么为什么GNU C中要支持零长度数组,它的好处是什么,它如何使使用,以及零长度数组主要用在什么场合:

(1)目的:是为了访问不定长结构体时节省空间和便利性。

(2)用法:在一个结构体的最后 ,申明一个长度为0的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为0的数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量, 数组名这个符号本身代表了一个不可修改的地址常量 (注意:数组名永远都不会是指针!),但对于这个数组的大小,我们可以进行动态分配。

实例:

[cpp] view plain copy
  1. struct demo  
  2. {  
  3.        int a;  
  4.        char b[256];  
  5.        char follow[0];  
  6. };  

现在程序中要分配一个struct demo结构体,并紧邻其后分配长度为LEN个字节空间,则可以使用如下方法得到:

structdemo *demo=(struct demo*)malloc(sizeof(struct demo)+LEN);

这样就可以用demo->fellow来访问结构体demo随后的空间数据,非常方便。当然也可以使用指针来达到这样的目的。

[cpp] view plain copy
  1. struct demo{  
  2.     int a;  
  3.     char b[256];  
  4.     char *follow;     
  5. };  

structdemo *demo=(struct demo *)malloc(sizeof(struct demo)+LEN);

同样可以达到零长度数组的效果,但是却多分配了一个char指针。如果分配额外数据空间还好,否则就是白白浪费了空间。

注:

在某一结构末尾如定义类似 charbytes[0] 的零长数组,表示该结构不定长,可通过数组的方式进行扩展。结构中必包含一个长度信息。结构本身类似于一个信息头。同时,此结构只能通过堆方式分配内存。

优点:比起在结构体中声明一个指针变量、再进行动态分配的办法,这种方法效率要高。因为在访问数组内容时,不需要间接访问,避免了两次访存。

实例:

[cpp] view plain copy
  1. #include   
  2. #include   
  3.   
  4. struct  test{  
  5.          int count;  
  6.          //reverse is array name;the array is no item;  
  7.         //the array address follow test struct  
  8.          int reverse[0];  
  9.  };  
  10.    
  11. int main()  
  12. {  
  13.          int i;  
  14.          struct test *ptest = (struct test *)malloc(sizeof(struct test)+sizeof(int)*10);  
  15.          for(i=0;i<10;i++){  
  16.                  ptest->reverse[i]=i+1;  
  17.          }  
  18.          for(i=0;i<10;i++){  
  19.                  printf("reverse[%d]=%d \n",i,ptest->reverse[i]);  
  20.          }  
  21.          printf("sizeof(struct test) =%d\n",sizeof(struct test));  
  22.          int a = *(&ptest->count +1 );  
  23.          printf("a=%d\n",a);  
  24.          return 0;  
  25.  }  

执行结果为:

jibo@jibo-VirtualBox:~/cv_work/work/zeor_arry $ ./main

reverse[0]=1

reverse[1]=2

reverse[2]=3

reverse[3]=4

reverse[4]=5

reverse[5]=6

reverse[6]=7

reverse[7]=8

reverse[8]=9

reverse[9]=10

sizeof(struct test) =4

a=1

分析:

可以看到test结构体中reverse数组并不占用空间。sizeofstruct test占用内存空间为4。并且可以看到结果体conunt变量之后就是零长度数组的内容。

九.范围标记

GCC还扩充了范围标记,通过它可以表示一段数值范围。这可以用在C程序的很多地方。最常用的莫过于switch/case 语句中。

实例:

[cpp] view plain copy
  1. static int sd_major(int major_idx)  
  2. {  
  3.     switch (major_idx) {  
  4.     case 0:  
  5.         return SCSI_DISK0_MAJOR;  
  6.     case 1 ... 7:  
  7.         return SCSI_DISK1_MAJOR + major_idx - 1;  
  8.     case 8 ... 15:  
  9.         return SCSI_DISK8_MAJOR + major_idx - 8;  
  10.     default:  
  11.         BUG();  
  12.         return 0;   /* shut up gcc */  
  13.     }  
  14. }  

范围标记还可以用来初始化数组里面的一些连续元素。如第五章中所介绍的数组。

十.给函数、变量和数据类型指定属性

属性是程序员向编译器传送信息或命令的工具,通常用它们来命令编译器在编译程序的时候,帮我们完成某些特殊的处理。属性可以指定到不同种类的对象上,包括函数、变量和类型等。指定属性时,必须用关键字”__attribute__”然后再在后面跟上用两个圆括号括起来的属性列表,属性列表中的属性用逗号隔开。

使用方法如下:

[cpp] view plain copy
  1. __attrbitue__((attr_1,attr_2,attr_3))  

1. noreturn

function属性,noreturn 用于函数,表示该函数从不返回。这可以让编译器生成稍微优化的代码,最重要的是可以消除不必要的警告信息比如未初始化的变量。

2. format(ARCHETYPE,STRING-INDEX,FIRST-TO-CHECK)

function属性 ,format 用于函数,表示该函数使用printf, scanf 或strftime 风格的参数,使用这类函数最容易犯的错误是格式串与参数不匹配,指定format 属性可以让编译器根据格式串检查参数类型.

参数说明:

1>“archetype”指定是哪种风格;

2>“string-index”指定传入函数的第几个参数是格式化字符串

3>“first-to-check”指定从函数的第几个参数开始按上述规则进行检查

实例:

[cpp] view plain copy
  1. include/linux/kernel.h  
  2.  asmlinkage int printk(const char * fmt, ...)  __attribute__ ((format (printf, 1, 2)));  

3.unused:

function属性,unused属性用于函数和变量,表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。

4.deprecated:

function属性,deprecated指出函数已经被废弃,不应该再使用。如果试图使用已经废弃的函数,就会收到警告。还可以对类型和变量应用这个属性,促使开发人员尽可能少使用它们。

5.section ("section-name")

function属性,__attribute__中的section属性将函数或数据放入指定名为”section_name”输入段中而不是输出段中。

注:

在 linux 驱动程序设计中,模块加载函数前有一个 __init 宏,也用了attribute 的 section属性。

#define __init __attribute__ ((__section__(".init.text")))

在linux内核中,所有标示为__init的函数在链接的时候都放在.init.text这个段中。此外,所有的__init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些 __init 函数,并在初始化完成后释放 init 区段

在linux 内核源代码中,与段相关的重要宏定义有:

__init , __initdata, __exit, __exitdata 及类似的宏

6.aligned(ALIGNMENT)

function:可以在定义变量时,添加__attribut__,来决定是否使用内存对齐,或是内存对齐到几个字节。

实例:

[cpp] view plain copy
  1. struct i387_fxsave_struct {  
  2.         unsigned short cwd;  
  3.         unsigned short swd;  
  4.         unsigned short twd;  
  5.         unsigned short fop;  
  6.     } __attribute__ ((aligned (16)));  

表示该结构类型的变量以16字节对齐。

7.packed:

function属性,告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数对齐。

8.interrupt(“”):

在 ARM 平台上 "__attribute((interrupt(”IRQ”)))" 表示改函数是一个interrupt handler。

十一.可变参数宏

在 GNU C 中,宏可以接受可变数目的参数,就象函数一样。

实例:

[cpp] view plain copy
  1. include/linux/kernel.h  
  2. #define pr_debug(fmt,arg...) \  
  3.      printk(KERN_DEBUG fmt,##arg)  

说明:arg表示其余的参数,可以是零个或多个,这些参数以及参数之间的逗号构成arg的值,在宏扩展时替换arg

例如:

[cpp] view plain copy
  1. pr_debug("%s:%d",filename,line)  

扩展为

[cpp] view plain copy
  1. printk("<7>" "%s:%d", filename, line)  

使用 ## 的原因是处理 arg 不匹配任何参数的情况,这时 arg 的值为空,GNUC 预处理器在这种特殊情况下,丢弃 ## 之前的逗号,这样

[cpp] view plain copy
  1. pr_debug("success!\n")  

扩展为

[cpp] view plain copy
  1. printk("<7>" "success!\n")  

注意最后没有逗号。

十二.内建函数

GNUC 提供了大量的内建函数,其中很多是标准C库函数的内建版本,例如memcpy,它们与对应的C库函数功能相同。还有一些其它内建函数,它们的名字通常以__builtin开始。

1.__builtin_return_address(LEVEL)

内建函数__builtin_return_address返回当前函数或其调用者的返回地址,参数LEVEL指定在栈上搜素框架的个数,0表示当前函数的返回地址,1表示当前函数的调用者的返回地址。

实例:

[cpp] view plain copy
  1. kernel/sched.c  
  2.    printk(KERN_ERR "schedule_timeout: wrong timeout"  
  3.     "value %lx from %p\n", timeout,  
  4.         __builtin_return_address(0));  

2.__builtin_constant_p(EXP)

内建函数__builtin_constant_p用于判断一个值是否为编译时常数,如果参数EXP的值是常数,函数返回1,否则返回0.

实例:

[cpp] view plain copy
  1. include/asm-i386/bitops.h  
  2. #define test_bit(nr,addr)             \  
  3.        (__builtin_constant_p(nr) ?     \  
  4.       constant_test_bit((nr),(addr)) :   \  
  5.        variable_test_bit((nr),(addr)))  

说明:很多计算或操作在参数为常数时有更优化的实现,在GNU C中用上面的方法可以根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在参数是常数时编译出最优化的代码。

3.__builtin_expect(EXP,C)

内建函数__builtin_expect用于为编译器提供分支预测信息,其返回值是整数表达式EXP的值,C的值必须是编译时常数。

实例:

[cpp] view plain copy
  1. include/linux/compiler.h  
  2. #define       likely(x)         __builtin_expect((x),1)  
  3. #define       unlikely(x)      __builtin_expect((x),0)  
  4.   
  5. kernel/sched.c  
  6.                if(unlikely(in_interrupt())) {  
  7.                  printk("Scheduling in interrupt\n");  
  8.                  BUG();  
  9.          }  

这个内建函数的语义是 EXP 的预期值是 C,编译器可以根据这个信息适当地重排语句块的顺序,使程序在预期的情况下有更高的执行效率。上面的例子表示处于中断上下文是很少发生的。

阅读(2131) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:没有了

给主人留下些什么吧!~~