天天看點

程式設計:GNU C 擴充之__attribute__ 機制簡介

原文:http://www.groad.net/bbs/read.php?tid=1032

GNU C的一大特色(卻不被初學者所知)就是__attribute__機制。__attribute__可以設定函數屬性(Function Attribute)、變量屬性(Variable Attribute)和類型屬性(Type Attribute)。

__attribute__書寫特征是:__attribute__前後都有兩個下劃線,并切後面會緊跟一對原括弧,括弧裡面是相應的__attribute__參數。

__attribute__文法格式為:

__attribute__ ((attribute-list))

其位置限制為:

放于聲明的尾部“;”之前。

函數屬性(Function Attribute)

函數屬性可以幫助開發者把一些特性添加到函數聲明中,進而可以使編譯器在錯誤檢查方面的功能更強大。__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,代碼如下:

1:

2:extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));

3:

4:void test()

5:{

6:myprint("i=%d\n",6);

7:myprint("i=%s\n",6);

8:myprint("i=%s\n","abc");

9:myprint("%s,%d,%d\n",1,2);

10:}

運作$gcc –Wall –c attribute.c attribute後,輸出結果為:

attribute.c: In function `test':

attribute.c:7: warning: format argument is not a pointer (arg 2)

attribute.c:9: warning: format argument is not a pointer (arg 2)

attribute.c:9: warning: too few arguments for format

如果在attribute.c中的函數聲明去掉__attribute__((format(printf,1,2))),再重新編譯,既運作$gcc –Wall –c attribute.c attribute後,則并不會輸出任何警告資訊。

注意,預設情況下,編譯器是能識别類似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

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

加上__attribute__((noreturn))則可以很好的處理類似這種問題。把

extern void myexit();

修改為:

extern void myexit() __attribute__((noreturn));

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

__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()的函數是不适合使用該屬性的。

-finstrument-functions

該參數可以使程式在編譯時,在函數的入口和出口處生成instrumentation調用。恰好在函數入口之後并恰好在函數出口之前,将使用目前函數的位址和調用位址來調用下面的profiling 函數。(在一些平台上,__builtin_return_address不能在超過目前函數範圍之外正常工作,是以調用位址資訊可能對profiling函數是無效的。)

void __cyg_profile_func_enter(void *this_fn, void *call_site);

void __cyg_profile_func_exit(void *this_fn, void *call_site);

其中,第一個參數this_fn是目前函數的起始位址,可在符号表中找到;第二個參數call_site是指調用處位址。

instrumentation 也可用于在其它函數中展開的内聯函數。從概念上來說,profiling調用将指出在哪裡進入和退出内聯函數。這就意味着這種函數必須具有可尋址形式。如果函數包含内聯,而所有使用到該函數的程式都要把該内聯展開,這會額外地增加代碼長度。如果要在C 代碼中使用extern inline聲明,必須提供這種函數的可尋址形式。

可對函數指定no_instrument_function屬性,在這種情況下不會進行instrumentation操作。例如,可以在以下情況下使用no_instrument_function屬性:上面列出的profiling函數、高優先級的中斷例程以及任何不能保證profiling正常調用的函數。

no_instrument_function

如果使用了-finstrument-functions,将在絕大多數使用者編譯的函數的入口和出口點調用profiling函數。使用該屬性,将不進行instrument操作。

constructor/destructor

若函數被設定為constructor屬性,則該函數會在main()函數執行之前被自動的執行。類似的,若函數被設定為destructor屬性,則該函數會在main()函數執行之後或者exit()被調用後被自動的執行。擁有此類屬性的函數經常隐式的用在程式的初始化資料方面。

這兩個屬性還沒有在面向對象C中實作。

同時使用多個屬性

可以在同一個函數聲明裡使用多個__attribute__,并且實際應用中這種情況是十分常見的。使用方式上,你可以選擇兩個單獨的__attribute__,或者把它們寫在一起,可以參考下面的例子:

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)));

如果帶有該屬性的自定義函數追加到庫的頭檔案裡,那麼是以調用該函數的程式都要做相應的檢查。

struct foo { int x[2] __attribute__ ((aligned (8))); };

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

short array[3] __attribute__ ((aligned));

Reference:

1.有關__attribute__的相對簡單的介紹:

2.__attribute__詳細介紹:

繼續閱讀