GNU C 擴展之__attribute__ 機制簡介

摘要:
   
在學習 linux 內核代碼及一些開源軟件的源碼(如: DirectFB ),經常可以看到有關 __attribute__ 的相關使用。本文結合自己的學習經歷,較爲詳細的介紹了 __attribute__ 相關語法及其使用。

----------------------------------------------------------------
聲明:
    此文爲原創,歡迎轉載,轉載請保留如下信息
    作者:聶飛(afreez)  北京-中關村
    聯繫方式:[email protected] (歡迎與作者交流)
    初次發佈時間:2006-06-17
    不經本人同意,不得用語商業或贏利性質目的,否則,作者有權追究相關責任!
------------------------------------------------------------------------------------------------------

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__ ,或者把它們寫在一起,可以參考下面的例子:

 

/* 
把類似
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)))
;
 

如果帶有該屬性的自定義函數追加到庫的頭文件裏,那麼所以調用該函數的程序都要做相應的檢查。

 

和非 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, ...)
{
               
/* 
函數定義
 */
}
 
更多的屬性含義參考:
http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Function-Attributes.html


 

變量屬性( Variable Attributes

關鍵字 __attribute__ 也可以對變量( variable )或結構體成員( structure field )進行屬性設置。這裏給出幾個常用的參數的解釋,更多的參數可參考本文給出的連接。

在使用 __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 字節對齊也是無濟於事的。

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 dlexport 等,

詳細信息可參考:

http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Variable-Attributes.html#Variable-Attributes

類型屬性( 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 字節對齊也是無濟於事的。

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

 

其它屬性的含義見:

http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Type-Attributes.html#Type-Attributes

變量屬性與類型屬性舉例

下面的例子中使用 __attribute__ 屬性定義了一些結構體及其變量,並給出了輸出結果和對結果的分析。

程序代碼爲:

 

struct p

{

int a;

char b;

char c;

}__attribute__((aligned(4))) pp;

 

struct q

{

int a;

char b;

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

 

return 0;

}

 

輸出結果:

 

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

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)

 

更詳細的介紹見: http://gcc.gnu.org

下面是一些便捷的連接: GCC 4.0 Function Attributes GCC 4.0 Variable Attributes GCC 4.0 Type Attributes GCC 3.2 Function Attributes GCC 3.2 Variable Attributes GCC 3.2 Type Attributes GCC 3.1 Function Attributes GCC 3.1 Variable Attributes

 

Reference

1 .有關 __attribute__ 的相對簡單的介紹: http://www.unixwiz.net/techtips/gnu-c-attributes.html

2 __attribute__ 詳細介紹: http://gcc.gnu.org

發佈了54 篇原創文章 · 獲贊 12 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章