程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> GNU C中的__attribute__機制簡介

GNU C中的__attribute__機制簡介

編輯:關於C語言

1. __attribute__

GNU C的一大特色(卻不被初學者所知)就是 __attribute__機制。

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

__attribute__前後都有兩個下劃線,並且後面會緊跟一對原括弧,括弧 裡面是相應的__attribute__參數

__attribute__語法格式為:

__attribute__ ( ( attribute-list ) )

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

__attribute__機制也很容易同 非GNU應用程序做到兼容。

GNU CC需要使用 –Wall,這是控制警告信息的 一個很好的方式。下面介紹幾個常見的屬性參數。

2. format

該屬 性可以使編譯器檢查函數聲明和函數實際調用參數之間的格式化字符串是否匹配 。它可以給被聲明的函數加上類似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 –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的“標准”庫函數。

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

之 後,編譯不會再出現警告信息。

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

5. 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是調用處地址 。

6. instrumentation

也可用於在其它函數中展開的內聯函數。 從概念上來說,profiling調用將指出在哪裡進入和退出內聯函數。這就意味著這 種函數必須具有可尋址形式。如果函數包含內聯,而所有使用到該函數的程序都 要把該內聯展開,這會額外地增加代碼長度。如果要在C 代碼中使用extern inline聲明,必須提供這種函數的可尋址形式。
可對函數指定 no_instrument_function屬性,在這種情況下不會進行 instrumentation操作。 例如,可以在以下情況下使用no_instrument_function屬性:上面列出的 profiling函數、高優先級的中斷例程以及任何不能保證profiling正常調用的函 數。

no_instrument_function

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

7. constructor/destructor

若函數被設定為constructor屬性,則該函數會 在main()函數執行之前被自動的執行。類似的,若函數被設定為destructor屬 性,則該函數會在main()函數執行之後或者exit()被調用後被自動的執行。 擁有此類屬性的函數經常隱式的用在程序的初始化數據方面,這兩個屬性還沒有 在面向對象C中實現。

8. 同時使用多個屬性

可以在同一個函數聲 明裡使用多個__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)) );

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

9. 和非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

10. 變量屬性(Variable Attributes)

關鍵字__attribute__也可以 對變量(variable)或結構體成員(structure field)進行屬性設置。

在使用__attribute__參數時,你也可以在參數的前後都加上“__”(兩個下劃線 ),例如,使用__aligned__而不是aligned,這樣,你就可以在相應的頭文件裡 使用它而不用關心頭文件裡是否有重名的宏定義。

11. 類型屬性(Type Attribute)

關鍵字__attribute__也可以對結構體(struct)或共用體( union)進行屬性設置。

大致有六個參數值可以被設定:aligned,packed ,transparent_union,unused,deprecated,may_alias

12. aligned (alignment)

該屬性設定一個指定大小的對齊格式(以字節為單位),例 如:

struct S { short f[3]; } __attribute__ ( ( aligned (8) ) );

typedef  int  more_aligned_int __attribute__ ( ( aligned (8) ) );

這裡,如果sizeof(short)的大小為2(byte),那麼 ,S的大小就為6。取一個2的次方值,使得該值大於等於6,則該值為8,所以編譯 器將設置S類型的對齊方式為8字節。該聲明將強制編譯器確保(盡它所能)變量 類型為struct S或者more-aligned-int的變量在分配空間時采用8字節對齊方式。

如上所述,你可以手動指定對齊的格式,同樣,你也可以使用默認的對齊 方式。例如:

struct S { short f[3]; } __attribute__ ( (aligned) );

上面,aligned後面不緊跟一個指定的數字值,編譯器將依據你的目標 機器情況使用最大最有益的對齊方式。

int  x __attribute__ ( (aligned (16) ) )  =  0;

編譯器將以16字節(注意是字節 byte不是位bit)對齊的方式分配一個變量。也可以對結構體成員變量設置該屬性 ,例如,創建一個雙字對齊的int對,可以這麼寫:

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

選擇針對目標機器最大的對齊方式,可以提高拷貝操作的效率。
aligned屬 性使被設置的對象占用更多的空間,相反的,使用packed可以減小對象占用的空 間。
需要注意的是,attribute屬性的效力與你的連接器也有關,如果你的 連接器最大只支持16字節對齊,那麼你此時定義32字節對齊也是無濟於事的。

13. packed

使用該屬性可以使得變量或者結構體成員使用最小的 對齊方式,即對變量是一字節對齊,對域(field)是位對齊。使用該屬性對 struct或者union類型進行定義,設定其類型的每一個變量的內存約束。當用在 enum類型定義時,暗示了應該使用最小完整的類型 (it indicates that the smallest integral type should be used)。

下面的例子中,x成員變量 使用了該屬性,則其值將緊放置在a的後面:

struct  test

{

     char  a;

     int  x[2] __attribute__ ((packed));

};

下面的 例子中,my-packed-struct類型的變量數組中的值將會緊緊的靠在一起,但內部 的成員變量s不會被“pack”,如果希望內部的成員變量也被packed,my- unpacked-struct也需要使用packed進行相應的約束。

struct my_packed_struct

{

       char  c;

       int  i;

       struct  my_unpacked_struct  s;

} __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

14. 變量屬性與類型屬性舉例

下面的例子中使用__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<2^3=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<2^4+8=24= sizeof(qq)

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

下面是一些便捷的連接:

GCC 4.0 Function Attributes

GCC 4.0 Variable Attributes

GCC 4.0 Type Attributes

15. Ref

簡單__attribute__介紹: http://www.unixwiz.net/techtips/gnu-c-attributes.html

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved