百尺竿頭更進一步——編譯器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<v;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行也可以直接去掉。







     



  







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章