三國殺是個很有意思的遊戲,在遊戲的過程中有一些封號有意思,“初嘗勝果”,“旗開得勝”....都是剛開始的勝利,可圈可點;然而高手與熟手的區別在於能否對自己用的工具領悟得更多。
在這個技術知識爆炸的年代,很多人都是掌握一門基本技藝,然後重複之,如此了此一生;而學習一門技藝,卻需要不斷地去淬鍊它,用力夯實基礎,以此爲基,跟進一步,集之大成。
在熟練使用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行也可以直接去掉。