GNU C的一大特色(卻不被初學者所知)就是__attribute__機制。__attribute__可以設置函數屬性(Function Attribute)、變量屬性(Variable Attribute)和類型屬性(Type Attribute)。它的書寫特徵是:__attribute__前後都有兩個下劃線,並切後面會緊跟一對原括弧,括弧裏面是相應的__attribute__參數,語法格式如下:
__attribute__ ((attribute-list))
另外,它必須放於聲明的尾部“;”之前。
函數屬性
函數屬性可以幫助開發者把一些特性添加到函數聲明中,從而可以使編譯器在錯誤檢查方面的功能更強大。__attribute__機制也很容易同非GNU應用程序做到兼容之功效。
GNU CC需要使用 –Wall編譯器來擊活該功能,這是控制警告信息的一個很好的方式。下面介紹幾個常見的屬性參數。
__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:第幾個參數爲格式化字符串(format string);
n:參數集合中的第一個,即參數“…”裏的第一個參數在函數參數總數排在第幾,注意,有時函數參數裏還有“隱身”的呢,後面會提到;
在使用上,__attribute__((format(printf,m,n)))是常用的,而另一種卻很少見到。下面舉例說明,其中myprint爲自己定義的一個帶有可變參數的函數,其功能類似於printf:
//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編譯後會提示format argument is not a pointer的警告。若去掉__attribute__((format(printf,1,2))),則會正常編譯。需要注意的是,編譯器只能識別類似printf的標準輸出庫函數。
還有一個__attribute__ noreturn,該屬性通知編譯器函數從不返回值,當遇到類似函數需要返回值而卻不可能運行到返回值處就已經退出來的情況,該屬性可以避免出現錯誤信息。C庫函數中的abort()和exit()的聲明格式就採用了這種格式,如下所示:
extern void exit(int) __attribute__((noreturn));
extern void abort(void) __attribute__((noreturn));
爲了方便理解,大家可以參考如下的例子:
- //name: noreturn.c ;測試__attribute__((noreturn))
- extern void myexit();
- int test(int n)
- {
- if ( n > 0 )
- {
- myexit();
- /* 程序不可能到達這裏*/
- }
- else
- return 0;
- }
編譯後的輸出結果如下:
$gcc –Wall –c noreturn.c
noreturn.c: In function `test':
noreturn.c:12: warning: control reaches end of non-void function
很顯然,這是因爲一個被定義爲有返回值的函數卻沒有返回值。加上__attribute__((noreturn))則可以解決此問題的出現。
後面還有__attribute__const、-finstrument-functions、no_instrument_function等的屬性描述,就不多轉了,感興趣的可以看原文。
變量屬性(Variable Attribute)
關鍵字__attribute__也可以對變量或結構體成員進行屬性設置。這裏給出幾個常用的參數的解釋,更多的參數可參考原文給出的連接。
在使用__attribute__參數時,你也可以在參數的前後都加上“__”(兩個下劃線),例如,使用__aligned__而不是aligned,這樣,你就可以在相應的頭文件裏使用它而不用關心頭文件裏是否有重名的宏定義。
aligned (alignment)
該屬性規定變量或結構體成員的最小的對齊格式,以字節爲單位。例如:
int x __attribute__ ((aligned (16))) = 0;
編譯器將以16字節(注意是字節byte不是位bit)對齊的方式分配一個變量。也可以對結構體成員變量設置該屬性,例如,創建一個雙字對齊的int對,可以這麼寫:
struct foo { int x[2] __attribute__ ((aligned (8))); };
如上所述,你可以手動指定對齊的格式,同樣,你也可以使用默認的對齊方式。如果aligned後面不緊跟一個指定的數字值,那麼編譯器將依據你的目標機器情況使用最大最有益的對齊方式。例如:
short array[3] __attribute__ ((aligned));
選擇針對目標機器最大的對齊方式,可以提高拷貝操作的效率。aligned屬性使被設置的對象佔用更多的空間,相反的,使用packed可以減小對象佔用的空間。
需要注意的是,attribute屬性的效力與你的連接器也有關,如果你的連接器最大隻支持16字節對齊,那麼你此時定義32字節對齊也是無濟於事的。
使用該屬性可以使得變量或者結構體成員使用最小的對齊方式,即對變量是一字節對齊,對域(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,dlexport等。
類型屬性(Type Attribute)
關鍵字__attribute__也可以對結構體(struct)或共用體(union)進行屬性設置。大致有六個參數值可以被設定,即:aligned, packed, transparent_union, unused, deprecated 和 may_alias。
在使用__attribute__參數時,你也可以在參數的前後都加上“__”(兩個下劃線),例如,使用__aligned__而不是aligned,這樣,你就可以在相應的頭文件裏使用它而不用關心頭文件裏是否有重名的宏定義。
aligned (alignment)
該屬性設定一個指定大小的對齊格式(以字節爲單位),例如:
struct S { short f[3]; } __attribute__ ((aligned (8)));
typedef int more_aligned_int __attribute__ ((aligned (8)));
該聲明將強制編譯器確保(盡它所能)變量類型爲struct S或者more-aligned-int的變量在分配空間時採用8字節對齊方式。
如上所述,你可以手動指定對齊的格式,同樣,你也可以使用默認的對齊方式。如果aligned後面不緊跟一個指定的數字值,那麼編譯器將依據你的目標機器情況使用最大最有益的對齊方式。例如:
struct S { short f[3]; } __attribute__ ((aligned));
這裏,如果sizeof(short)的大小爲2(byte),那麼,S的大小就爲6。取一個2的次方值,使得該值大於等於6,則該值爲8,所以編譯器將設置S類型的對齊方式爲8字節。
aligned屬性使被設置的對象佔用更多的空間,相反的,使用packed可以減小對象佔用的空間。
需要注意的是,attribute屬性的效力與你的連接器也有關,如果你的連接器最大隻支持16字節對齊,那麼你此時定義32字節對齊也是無濟於事的。
使用該屬性對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__屬性定義了一些結構體及其變量,並給出了輸出結果和對結果的分析。
分析:
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
sizeof(qn)=8;即qn是採用8字節對齊的,所以要在a,b後面添3個空餘字節,然後才能存儲qn,
4+1+(3)+8+1=17
因爲qq採用的對齊是8字節對齊,所以qq的大小必定是8的整數倍,即qq的大小是一個比17大又是8的倍數的一個最小值,由此得到
17<24+8=24= sizeof(qq)