百尺竿头更进一步——编译器gcc对c语言的扩展

       三国杀是个很有意思的游戏,在游戏的过程中有一些封号有意思,“初尝胜果”,“旗开得胜”....都是刚开始的胜利,可圈可点;然而高手与熟手的区别在于能否对自己用的工具领悟得更多。

       在这个技术知识爆炸的年代,很多人都是掌握一门基本技艺,然后重复之,如此了此一生;而学习一门技艺,却需要不断地去淬炼它,用力夯实基础,以此为基,跟进一步,集之大成。

      在熟练使用gcc作为编译工具的基础上,还需要理解gnc的c语言的扩展;标准的c语言更加通用,能够在各种平台上使用,而c语言的方言,更加灵活,方便。在此基础上,随着linux平台的广泛使用,gnu的c语言扩展成为一项很有用的技艺,值得让我们去学习。

      gnu c是在标准c语言的基础上,做了很多扩展,可以看作一种C语言的方言。如下是在gcc使用手册(https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/index.html#toc_C-Extensions第六节——Extensions to the C Language Family)上摘取的一些有利于c语言编程的扩展;同样这些技巧也是被反复使用在linux内核的源码中与其他开发源代码中;其实,这也是很好的一种总结的方法,将好的编程方法用程序库,应用工具,文档,甚至语言积累下来。

  如下为一系列常用的实例与简单实用,更详细的,请参考官方文档;具体实例,请参考附件的c文件。

  1.表达式的扩展以({})形式进行扩展,例子如下:

({ int y = foo (); int z;

 if (y > 0) z = y;

       else z = - y; 

       z; })//z可以看作表达式输出

   使用场景:将复杂的表达式作为宏放到代码中使用,让代码更加简洁,易懂。

  2.局部标记的支持,用__label__关键字声明一个标签,然后用goto跳转;标签在程序中的表现就是地址,这样处理标签,可以更加灵活的控制程序流程。

#define SEARCH(value, array, target)              \

     do {                                              \//将复杂的逻辑定义为宏的一种技巧。

       __label__ found;                                \//局部标记的定义

      typeof (target) _SEARCH_target = (target);      \//typeof标准字的使用,得到target的类型

       typeof (*(array)) *_SEARCH_array = (array);     \

       int i, j;                                       \

       int value;                                      \

       for (i = 0; i < max; i++)                       \

         for (j = 0; j < max; j++)                     \

          if (_SEARCH_array[i][j] == _SEARCH_target)  \

             { (value) = i; goto found; }              \//局部标记的使用

       (value) = -1;                                   \

      found:;                                          \//局部标记的实现

     } while (0)

   

    3.将标签当作变量来使用,标签被以"&&"开头来引用,被以"void*"的指针来指向,被goto时需要用*来得到其值。

int label_val(int i,int type)

{

   void*label_array[]={&&do_add,&&do_sub,&&do_multi,&&do_div};//定义标签数组

   goto *label_array[type];//标签被引用

do_add:

   return i++;

do_sub:

   return i--;

do_multi:

   return i*i;

do_div:

   return 1/i;

}

   4.函数内部定义函数,只能在被定义的函数内部访问,这样就控制了函数的访问权限,更有利于模块化。

int nested_func(int a,int b)

{

auto int add();//定义 内部add函数,需要auto声明

int do_add(int(*add)()){//在定义内部函数,调用内部函数,能够引用参数

return add();

}

int c = do_add(add);//内部函数被调用

if(c>0)

return c;

else

return 0;

int add(){ return a+b;}//内部函数实现

}

   5.内置函数,增强c语言对程序的控制,分为几类:

   a)获取函数调用关系的函数,有利于调试函数的调用堆栈:__builtin_return_address(int level).得到调用者的地址。

   b)获取调用函数传递的参数,间接调用函数以及得到函数的返回值:void * __builtin_apply_args ()得到参数列表。

   6.关键字typeof的使用,获取变量的类型定义,能够动态获取数据类型,更加灵活,准确的描述数据类型。

    #define max(a,b) \

       ({ typeof (a) _a = (a); \

           typeof (b) _b = (b); \

         _a > _b ? _a : _b; })

   7.扩展标准c表达式的灵活处理,让程序更加容易读懂:

   a)结构体与数组的初始化

   b)case 语句的连续表示

   c)"?:"运算符的简化

   e)长度为0的数组:定义时,节约空间

   f)长度可变的数组,动态数组的支持

   g)可变参数的宏定义

   h)数组非常量初始化

   i)灵活的动态数据转换

   j)混合声明与定义

   k)offsetof(type,member)的使用:得到member在结构体type中的偏移(stddef.h)中定义,一个重要的实例——container_of(通过局部指针得到整体指针),在linux内核中,链表的使用

#define container_of(ptr, type, member) ({\

const typeof( ((type *)0)->member ) *__mptr = (ptr);\

(type *)( (char *)__mptr - offsetof(type,member) );})

//offsetof的扩展,通过结构体(type)的域(member)指针(ptr)得到指向该结构体的指针。

  #define debug(format, ...) fprintf (stderr, format,## __VA_ARGS__)

//定义可变参数的宏,"..."对应"##__VA_ARGS__"支持只有format参数。

  #define warning(format0,args...) fprintf(stdout,format0, args)

void basic_extend()

{

int array[5] = {[2]=1,[3 ... 4]=3};//数组的初始化,第2个参数为1,第3到4个参数为3(注意“...”旁边需要有空格)

 struct point { int x, y; };

 struct point p0 = {//结构体的初始化

 .x = 5,

 .y =6

 };

int i =0;

int v = 8 ;

for(i=0;i<5;i++){

switch(i){

case 1 ... 3 ://case语句的连续表达

 v = array[i];

break;

case 4:

 v = array[i]?:p0.y;//"?:"的简化表达

break;

}

}

struct line {

int len;

char  p[0];//长度为0的数组声明

};

struct line *one;//混合声明与定义的表达

int len = 5;

one = (struct line *)malloc(sizeof(struct line)+len+1);

one->len = len;

strcpy(one->p,"this");

printf("one.p=%s\n",one->p);

free(one);

  struct S { int x[v]; };//数组的长度动态设置

  struct S Sex ;

  printf("size Sex:%d\n",sizeof(Sex));

  for(len =0;len

  Sex.x[len] = len;

  printf("%d\n",Sex.x[len]);

  }

  int av[4] = {v ,len,Sex.x[2] ,i};//数组的动态初始化

  printf("av[2] = %d\n",av[2]);

  struct Fst {int a;char b[2]} fst;

  fst = ( struct Fst ){v,len,i};

  printf("fst.b[1]=%d\n",fst.b[1]);

  static short sb[] = (short[3]){'6',8,9};

  char *fst_byts = (char*)&fst;

  printf("sb[0]=%d\n",sb[0]);

 printf("offsetof(struct Fst,a) = %d\n",offsetof(struct Fst,a)); //offset的实例

 printf("v:%d\n",fst.a);

 char *pb = fst.b;

 struct Fst *pst = container_of(pb,struct Fst,b);//container_of的实例

 printf("pst'a :%d\n",pst->a);

}


  8.gnu 属性(__attribute__)的使用,增加c语言的表达能力,增强在编译与链接时对程序的控制,主要用于函数,变量与数据结构定义。具体的属性含义与使用,请参考gnu的官方网址:https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Attribute-Syntax.html#Attribute-Syntax

  a)编译对函数调用的优化,对程序进行更严格的检测,用于声明函数,不能用于函数定义->

 https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Function-Attributes.html#Function-Attributes

  b)指定变量的相关属性,比如:对齐,链接到新的段等->

 https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Variable-Attributes.html#Variable-Attributes

  c)指定数据结构定义的属性,比如:对齐,数据分布等->

 https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Type-Attributes.html#Type-Attributes

void attr_func() __attribute__((aligned(16)));//函数属性的定义

void attr_func()

{

struct Attr1{

int a;//4

short b;//2

char c;//2偶地址对齐

};

struct Attr_P{

int a;//4

short b;//2

char c;//1

}__attribute__((packed));//结构体属性的定义

int a __attribute__((aligned(32))) = 6;//变量属性的定义

printf("a addr:0x%x\n",&a);

printf("Attr1 size:%d\n",sizeof(struct Attr1));

printf("Attr_P size:%d\n",sizeof(struct Attr_P));

printf("%s addr:0x%x\n",__func__,attr_func);

}


9.volatile 属性的使用,告之编译器,该对象不能够被优化处理。

10.asm用于c语言内嵌汇编。主要有如下3方面的用途:

 a)实现c语言无法实现的操作,或者为c语言提供底层的接口

 b)控制程序运行状态,比如:读取与修改寄存器值

 c)优化c语言程序,在程序关键路径上,用汇编代替c语言实现对应功能

 实现形式:

 asm(汇编模板:输出参数列表:输入参数列表:其他约束);

 (1)汇编模板——GNU用的AT&T的汇编语法

  学习与AT&T汇编的方法:

  (i)熟悉Intel汇编语法,学习它是因为有很多实例与教程,通过NASM命令来汇编对应的汇编程序。

  生成elf文件(默认生成bin文件):nasm -f elf asm.elf

 (ii)AT&T汇编语法,了解AT&T语法与Intel语法的差异,可参考附件《gas_manual-unix汇编

 (iii)汇编转换用objdump来进行反汇编成对应的汇编语言,然后通过对比,来理解与加深对汇编的理解

  AT&T汇编:objdump -j .text -d asm.elf -m i386

  Intel汇编:objdump -j .text -d asm.elf -m i386:intel

 汇编模板:主要是汇编指令——操作码,操作数(固定操作数,替换操作码——由输入/输出列表指定)

 参数列表:主要是建立c语言变量与汇编操作数的对应规则——参数替换式与引用式;c语言一般的变量是保存在内存(局部变量是在堆栈中,全局变量是在数据段)中的(除非用register指定),所以一般要运算需要将参数放到寄存器中——变量要运算需要寄存器作为载体。参数列表格式:约束(c语言变量表达式)

 多条指定的支持:多条指令,以“\n\r”或者“;”分割。

(2)输出列表

  将c语言的变量作为参数放到汇编运算的写入的位置,将汇编操作数中的值写入到输出的列表

(3)输入列表

  将c语言的变量作为参数放入到汇编模板中指定的位置

(4)相关约束(clobber list

  汇编指令列表中的相关操作数(寄存器列表或者“memory”),会被其他方法修改的约束,比如:硬件或者其他软件指令流会对该操作数进行影响。

 

  11.使用代码覆盖测试工具——gcov,对程序代码进行分析,从而达到优化代码的目的,也可参考https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Invoking-Gcov.html#Invoking-Gcov

以如下代码为例:

  (1)编辑源文件(gcov-c.c),如下:

 int main()
{
 int i=0;
 int sum =0;
 for(;i<10;i++)
  sum += i;
 if(sum >= 50)
 {
  printf("sum = %d >= 50\n",sum);
 }else{
  printf("sum = %d < 50\n",sum);
 }
 return 0;
}

 (2)gcc带测试参数编译:

  gcc -fprofile-arcs -ftest-coverage gcov-c.c -o gcov.out

 (3)执行生成的bin文件:./gcov.out

 (4)用gcov命令分析源文件,生成分析之后的文件gcov-c.c.gcov:

  gcov gcov-c.c

  (5)查看分析gcov-c.c.gcov文件:

      执行次数 源文件行号 源文件信息

         -:   0:Source:gcov-c.c
        -:    0:Graph:gcov-c.gcno
        -:    0:Data:gcov-c.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        1:    1:int main()
        -:    2:{
        1:    3: int i=0;
        1:    4: int sum =0;
       11:    5: for(;i<10;i++)
       10:    6:  sum += i;
        1:    7: if(sum >= 50)
        -:    8: {
    #####:    9:  printf("sum = %d >= 50\n",sum);
        -:   10: }else{
        1:   11:  printf("sum = %d < 50\n",sum);
        -:   12: }
        1:   13: return 0;
        -:   14:}

     以上文件可以看出,文件总共有14行,有8行被执行了。主要花费时间在5/6行中,其中第9行没有被执行。

     如果需要优化,则需要对5/6行进行优化,而第7/9行也可以直接去掉。







     



  







你可能感兴趣的:(开发工具,程序语言)