天天看點

懂了GNUC屬性,C大牛非你莫屬

懂了GNUC屬性,C大牛非你莫屬

圖檔發自簡書App

之前,小編黑鳥連續日更了十篇GNU C中特有的不同于ANSI C标準的文法擴充,這些擴充往往是普通教材中不會提及的。而恰恰是這些%1的文法影響着我們%99的人能否在衆多C程式員中脫穎而出,成為同僚眼中的大牛和boss眼中的紅人。

大家有沒有這種經曆,在自認為自己C語言水準已經達到某種境界的時候,興緻沖沖的拿起一本linux核心解析之類的書,準備下大決心啃完裡面的系統程式,看看業界大牛是怎麼編寫C語言程式的時候,往往在驚歎作者奇思妙想,做到人C合一的時候,發現裡面遍布了許多陌生的關鍵字或文法,而這些恰恰是精髓所在,隻可惜我們學藝不精,無法領略。

那麼,今天我将陸續推出系列GNU C中特有的文法擴充詳解,為你在C語言進階路上保駕護航!

一、linux gcc的屬性解析

  • GNU C的一大特色(卻不被初學者所知)就是​

    ​__attribute__​

    ​機制。​

    ​__attribute__​

    ​可以設定函數屬性(Function Attribute)、變量屬性(Variable Attribute)和類型屬性(Type Attribute)。
  • ​__attribute__​

    ​書寫特征是:​

    ​__attribute__​

    ​前後都有兩個下劃線,并切後面會緊跟一對原括弧,括弧裡面是相應的​

    ​__attribute__​

    ​參數。
  • ​__attribute__​

    ​文法格式為:

    ​__attribute__((attribute-list))​

    ​ 其位置限制為:放于聲明的尾部“;”之前。

1 函數屬性(FunctionAttribute)

函數屬性可以幫助開發者把一些特性添加到函數聲明中,進而可以使編譯器在錯誤檢查方面的功能更強大。​

​__attribute__​

​​機制也很容易同非GNU應用程式做到相容之功效。

GNU C需要使用–Wall編譯器來擊活該功能,這是控制警告資訊的一個很好的方式。下面介紹幾個

常見的屬性參數。

1 __attribute__format

  • 該​

    ​__attribute__​

    ​屬性可以給被聲明的函數加上類似printf或者scanf的特征,它可以使編譯器檢查函數聲明和函數實際調用參數之間的格式化字元串是否比對。該功能十分有用,尤其是處理一些很難發現的bug。
  • format的文法格式為:

    ​​

    ​format(archetype,string-index,first-to-check)​

  • format屬性告訴編譯器,按照printf, scanf, strftime或strfmon的參數表格式規則對該函數的參數進行檢查。

“archetype”指定是哪種風格;

“string-index”指定傳入函數的第幾個參數是格式化字元串;

“first-to-check”指定從函數的第幾個參數開始按上述規則進行檢查。

具體使用格式如下:

​​

​__attribute__((format(printf,m,n)))​

​​

​​

​__attribute__((format(scanf,m,n)))​

​​

其中參數m與n的含義為:

m:第幾個參數為格式化字元(formatstring);

n:參數集合中的第一個(即參數“…”裡的第一個參數)在函數參數總數排在第幾,注意,有時函數參數裡還有“隐身”的呢,後面會提到;

  • 一般​

    ​__attribute__((format(printf,m,n)))​

    ​是常用的,而另一種卻很少見到。下面舉例說明,
//m=1;n=2
extern void myprint(const char* format,...)__attribute__((format(printf,1,2)));
//m=2;n=3
extern void myprint(int l, const char* format, ...)__attribute__((format(printf,2,3)));      

需要特别注意的是,如果myprint是一個函數的成員函數,那麼m和n的值可有點“懸乎”了,例如:

//m=3;n=4
extern void myprint(int l, const char* format, ...)__attribute__((format(printf,3,4)));      

其原因是,類成員函數的第一個參數實際上是一個“隐身”的“this”指針。(有點C++基礎的都知道點

this指針,不知道你在這裡還知道嗎?)

這裡給出測試用例:attribute.c,代碼如下:

extern void myprint(const char* format, ...)__attribute__((format(printf,1,2)));
void test()
{
    myprint("i=%d\n",6);
    myprint("i=%s\n",6);
    myprint("i=%s\n","abc");
    myprint("%s,%d,%d\n",1,2);
}      

運作​

​$gcc –Wall –c attribute.cattribute​

​後,輸出結果為:

attribute.c: In function`test':
attribute.c:7:warning:format argument is not a pointer(arg2)
attribute.c:9:warning:format argument is not a pointer(arg2)
attribute.c:9:warning:too few arguments for format      

如果在attribute.c中的函數聲明去掉​

​__attribute__((format(printf,1,2)))​

​​,再重新編譯,即運作

​​

​$gcc –Wall –c attribute.c attribute​

​後,則并不會輸出任何警告資訊。

注意,預設情況下,編譯器是能識别類似printf的“标準”庫函數。

2 __attribute__noreturn

  • 該屬性通知編譯器函數從不傳回值,當遇到類似函數需要傳回值而卻不可能運作到傳回值處就已經退出來的情況,該屬性可以避免出現錯誤資訊。
  • C庫函數中的abort()和exit()的聲明格式就采用了這種格式,如下所示:
extern void exit(int)__attribute__((noreturn));
extern void abort(void)__attribute__((noreturn));      

為了友善了解,大家可以參考如下的例子:

extern void myexit();
int test(int n)
{
    if(n>0){
        myexit(); /*程式不可能到達這裡*/
    }
    else{
        ...
    }
    return0;
}      

編譯顯示的輸出資訊為:

$gcc –Wall –c noreturn.c
noreturn.c:In function `test':
noreturn.c:12:warning:control reaches end of non-void function      

警告資訊也很好了解,因為你定義了一個有傳回值的函數test卻有可能沒有傳回值,程式當然不知道怎麼辦了!加上​

​__attribute__((noreturn))​

​​則可以很好的處理類似這種問題。把​

​extern void myexit()​

​​修改為:​

​extern void myexit()__attribute__((noreturn))​

​之後,編譯不會再出現警告資訊。

3 __attribute__const

  • 該屬性隻能用于帶有數值類型參數的函數上。當重複調用帶有數值參數的函數時,由于傳回值是相同的,是以此時編譯器可以進行優化處理,除第一次需要運算外,其它隻需要傳回第一次的結果就可以了,進而可以提高效率。
  • 該屬性主要适用于沒有靜态狀态(static state)和副作用的一些函數,并且傳回值僅僅依賴輸入的參數。
  • 為了說明問題,下面舉個非常“糟糕”的例子,該例子将重複調用一個帶有相同參數值的函數,具體如下:
extern int square(int n)__attribute__((const));
...
for(i=0;i<100;i++)
{
    total+=square(5)+i;
}      

通過添加​

​__attribute__((const))​

​聲明,編譯器隻調用了函數一次,以後隻是直接得到了相同的一個傳回值。

事實上,const參數不能用在帶有指針類型參數的函數中,因為該屬性不但影響函數的參數值,同樣也影響到了參數指向的資料,它可能會對代碼本身産生嚴重甚至是不可恢複的嚴重後果。

并且,帶有該屬性的函數不能有任何副作用或者是通路全局或靜态變量,是以,類似getchar()或

time()的函數是不适合使用該屬性的。

4 同時使用多個屬性

  • 可以在同一個函數聲明裡使用多個​

    ​__attribute__​

    ​​,并且實際應用中這種情況是十分常見的。使用方式上,你可以選擇兩個單獨的​

    ​__attribute__​

    ​,或者把它們寫在一起,可以參考下面的例子:
/*把類似printf的消息傳遞給stderr并退出 */
extern void die(const char* format,...)__attribute__((noreturn))__attribute__((format(printf,1,2)));      

或者寫成

extern void die(const char* format,...)__attribute__((noreturn,format(printf,1,2)));      
  • 如果帶有該屬性的自定義函數追加到庫的頭檔案裡,那麼所有調用該函數的程式都要做相應的檢

    查。

5 和非GNU編譯器的相容性

  • 慶幸的是,​

    ​__attribute__​

    ​設計的非常巧妙,很容易做到和其它編譯器保持相容,也就是說,如果工作在其它的非GNU編譯器上,可以很容易的忽略該屬性。即使attribute使用了多個參數,也可以很容易的使用一對圓括弧進行處理,例如:
/*如果使用的是非GNU C,那麼就忽略__attribute__*/
#ifndef __GNUC__
#define __attribute__(x) /*NOTHING*/
#endif      
  • 需要說明的是,​

    ​__attribute__​

    ​适用于函數的聲明而不是函數的定義。是以,當需要使用該屬性的函數時,必須在同一個檔案裡進行聲明,例如:
/*函數聲明 */
void die(const char* format,...)__attribute__((noreturn))__attribute__((format(printf,1,2)));
void die(const char* format,...)
{
    /*函數定義 */
}      

2 變量屬性(VariableAttributes)

  • 關鍵字​

    ​__attribute__​

    ​也可以對變量(variable)或結構體成員(structurefield)進行屬性設定。這裡給出幾個常用的參數的解釋,更多的參數可參考本文給出的連接配接。
  • 在使用​

    ​__attribute__​

    ​參數時,你也可以在參數的前後都加上​

    ​__​

    ​(兩個下劃線),例如,使用​

    ​__aligned__​

    ​而不是​

    ​aligned​

    ​,這樣,你就可以在相應的頭檔案裡使用它而不用關心頭檔案裡是否有重名的宏定義。

1 aligned(alignment)

  • 該屬性規定變量或結構體成員的最小的對齊格式,以位元組為機關。例如在下面的聲明中,編譯器将以16位元組(注意是位元組byte不是位bit)對齊的方式配置設定一個變量。:
int x__attribute__((aligned(16)))=0;      
  • 也可以對結構體成員變量設定該屬性,例如,建立一個雙字對齊的int對,可以這麼寫:
struct foo{
    int x[2]__attribute__((aligned(8)));
};      
  • 同樣,你也可以使用預設的對齊方式。如果aligned後面不緊跟一個指定的數字值,那麼編譯器将依據你的目标機器情況使用最大最有益的對齊方式。例如:
short array[3]__attribute__((aligned));      
  • 選擇針對目标機器最大的對齊方式,可以提高拷貝操作的效率。aligned屬性使被設定的對象占用更多的空間,相反的,使用packed可以減小對象占用的空間。
  • 需要注意的是,attribute屬性的效力與你的連接配接器也有關,如果你的連接配接器最大隻支援16位元組對齊,那麼你此時定義32位元組對齊也是無濟于事的。

2 packed

  • 使用該屬性可以使得變量或者結構體成員使用最小的對齊方式,即對變量是一位元組對齊,對域(field)是位對齊。
  • 下面的例子中,x成員變量使用了該屬性,則其值将緊放置在a的後面:
struct test
{
    char a;
    int x[2]__attribute__((packed));
};      

其它可選的屬性值還可以是:cleanup,common,nocommon,deprecated,mode,section,shared,tls_model,transparent_union,unused,vector_size,weak,dllimport等。

詳細資訊可參考:

​​​GNUC變量屬性​​

3 類型屬性(Type Attribute)

  • 關鍵字​

    ​__attribute__​

    ​也可以對結構體(struct)或共用體(union)進行屬性設定。大緻有六個參數值可以被設定,即:aligned, packed, transparent_union, unused, deprecated和 may_alias。

aligned

  • 在使用​

    ​__attribute__​

    ​參數時,你也可以在參數的前後都加上​

    ​__​

    ​(兩個下劃線),例如,使用​

    ​__aligned__​

    ​而不是​

    ​aligned​

    ​,這樣,你就可以在相應的頭檔案裡使用它而不用關心頭檔案裡是否有aligned(alignment)。
  • 該屬性設定一個指定大小的對齊格式(以位元組為機關),例如:
struct S{shortf[3]; }__attribute__((aligned(8)));
typedef int more_aligned_int__attribute__((aligned(8)));      

該聲明将強制編譯器確定(盡它所能)變量類型為struct S或者more-aligned-int的變量在配置設定空

間時采用8位元組對齊方式。

  • 如上所述,你可以手動指定對齊的格式,同樣,你也可以使用預設的對齊方式。如果aligned後面不緊跟一個指定的數字值,那麼編譯器将依據你的目标機器情況使用最大最有益的對齊方式。例如:
struct S{shortf[3];}__attribute__((aligned));      

這裡,如果sizeof(short)的大小為2(byte),那麼,S的大小就為6。取一個2的次方值,使得該值

大于等于6,則該值為8,是以編譯器将設定S類型的對齊方式為8位元組。

  • aligned屬性使被設定的對象占用更多的空間,相反的,使用packed可以減小對象占用的空間。

    需要注意的是,attribute屬性的效力與你的連接配接器也有關,如果你的連接配接器最大隻支援16位元組對齊,那麼你此時定義32位元組對齊也是無濟于事的。

packed

  • 使用該屬性對struct或者union類型進行定義,設定其類型的每一個變量的記憶體限制。當用在enum類型定義時,暗示了應該使用最小完整的類型(it indicates that the smallest integral type should be used)。
  • 下面的例子中,my-packed-struct類型的變量數組中的值将會緊緊的靠在一起,但内部的成員變量s不會被“pack”,如果希望内部的成員變量也被packed的話,my-unpacked-struct也需要使用packed進行相應的限制。
struct my_unpacked_struct
{
    char c;
    int i;
};
struct my_packed_struct
{
    char c;
    int i;
    struct my_unpacked_struct s;
}__attribute__((__packed__));      

變量屬性與類型屬性舉例

下面的例子中使用​

​__attribute__​

​屬性定義了一些結構體及其變量,并給出了輸出結果和對結果的分析。程式代碼為:

struct p
{
    int a;
    char b;
    char c;
}__attribute__((aligned(4))) pp;

struct q
{
    int a;
    char b;
    struct p qn;
    char c;
}__attribute__((aligned(8))) qq;

int main()
{
 printf("sizeof(int)=%d,sizeof(short)=%d,sizeof(char)=%d\n",\
        sizeof(int),sizeof(short),sizeof(char));
printf("pp=%d,qq=%d\n",sizeof(pp),sizeof(qq));
return0;
}      

輸出結果:

sizeof(int)=4,sizeof(short)=2.sizeof(char)=1
pp=8,qq=24      
sizeof(pp):sizeof(a)+sizeof(b)+sizeof(c)=4+1+1=6<23=8=sizeof(pp)
sizeof(qq):sizeof(a)+sizeof(b)=4+1=5      

繼續閱讀