Linux核心入門—— __attribute__ 機制

來源:互聯網
上載者:User

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

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

__attribute__文法格式為:

__attribute__ ((attribute-list))

其位置約束為:放於聲明的尾部“;”之前。

1 設定函數屬性(Function Attribute)

函數屬性可以協助開發人員把一些特性添加到函式宣告中,從而可以使編譯器在錯誤檢查方面的功能更強大。__attribute__機制也很容易同非GNU應用程式做到相容之功效。

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

1.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:第幾個參數為格式化字串(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的“標準”庫函數。

1.2 __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));
之後,編譯不會再出現警告資訊。

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

1.4 -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正常調用的函數。

1.5 no_instrument_function

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

1.6 constructor/destructor

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

這兩個屬性還沒有在物件導向C中實現。

1.7 同時使用多個屬性

可以在同一個函式宣告裡使用多個__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)));

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

1.8 和非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.5.0/gcc/Function-Attributes.html

2 變數屬性(Variable Attributes)

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

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

2.1 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位元組對齊也是無濟於事的。

2.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,dlexport 等,詳細資料可參考:
http://gcc.gnu.org/onlinedocs/gcc-4.5.0/gcc/Variable-Attributes.html#Variable-Attributes

3 類型屬性(Type Attribute)

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

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

3.1 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位元組對齊也是無濟於事的。

3.2 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.5.0/gcc/Type-Attributes.html#Type-Attributes

4 變數屬性與類型屬性舉例

下面的例子中使用__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 

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

 

轉自:http://blog.csdn.net/yunsongice/article/details/5538020

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.