__attribute__中constructor和destructor


1、前言

  最近看到一份代碼,看到一個函數前面用__attribute__((destructor))修飾,當時感覺有點怪怪的,搜了整個程序,也沒發現哪個地方調用這個函數。於是從字面意思猜想,該函數會在程序結束后自動調用,與C++中的析構函數類似。第一次接觸GNU下的attribute,總結一下。

2、__attribute__介紹

  __attribute__可以設置函數屬性(Function Attribute)、變量屬性(Variable Attribute)和類型屬性(Type Attribute)。__attribute__前后都有兩個下划線,並且后面會緊跟一對原括弧,括弧里面是相應的__attribute__參數

  __attribute__語法格式為:__attribute__ ( ( attribute-list ) )

  若函數被設定為constructor屬性,則該函數會在main()函數執行之前被自動的執行。類似的,若函數被設定為destructor屬性,則該函數會在main()函數執行之后或者exit()被調用后被自動的執行。例如下面的程序:

復制代碼
#include <stdio.h> #include <stdlib.h> static int * g_count = NULL; __attribute__((constructor)) void load_file() { printf("Constructor is called.\n"); g_count = (int *)malloc(sizeof(int)); if (g_count == NULL) { fprintf(stderr, "Failed to malloc memory.\n"); } } __attribute__((destructor)) void unload_file() { printf("destructor is called.\n"); if (g_count) free(g_count); } int main() { return 0; }
復制代碼

程序執行結果如下:

3、參考網址

  關於__attribute__的更多更加詳細的介紹可以參考:

http://blog.csdn.net/polisan/article/details/5031142

http://blog.csdn.net/ithomer/article/details/6566739

 

 

GCC __attribute__((constructor)|(destructor))

在閱讀TGTD的代碼時發現了一個非常詭異的問題,聲明了一個空的全局數組,在使用的時候卻發現數組非空,在main()入口時數組已經非空.數組時在什么地方被賦值了呢?最后發現__attribute__這個東東在起作用,類似於全局變量類的構造函數在main()前被調用.

__attribute__((constructor))
__attribute__((destructor))

  1. /* test.c */  
  2.   
  3. #include<stdio.h>  
  4. __attribute__((constructor)) void before_main()  
  5. {  
  6.    printf("before main/n");  
  7. }  
  8.   
  9. __attribute__((destructor)) void after_main()  
  10. {  
  11.    printf("after main/n");  
  12. }  
  13.   
  14. int main()  
  15. {  
  16.    printf("in main/n");  
  17.    return 0;  
  18. }  

$ gcc test.c -o test
$ ./test
before main
in main
after main

根據上面的代碼以及輸出結果,我們可以猜到__attribute__((constructor))表示這段代碼將在main函數前調用,就像在C++里面的全局變量類的構造一樣.

說到C++里面的全局類對象的構造,我們不禁要問全局類對象的構造跟__attribute__((constructor))以及destructor誰在前誰在后呢?

/*test2.cpp*/

  1. #include<iostream>  
  2. using namespace std;  
  3. __attribute__((constructor)) void before_main()  
  4. {  
  5.     cout<<"Before Main"<<endl;  
  6. }  
  7. __attribute__((destructor)) void after_main()  
  8. {  
  9.     cout<<"After Main"<<endl;  
  10. }  
  11. class AAA{  
  12.     public:  
  13.     AAA(){  
  14.         cout<<"AAA construct"<<endl;  
  15.     }     
  16.     ~AAA(){  
  17.         cout<<"AAA destructor" <<endl;  
  18.     }     
  19. };  
  20. AAA A;  
  21. int main()  
  22. {  
  23.     cout<<"in main"<<endl;  
  24.     return 0;  
  25. }  

$ make test2

$ ./test2

AAA construct
Before Main
in main
AAA destructor
After Main

可以看到全局類的構造過程發生在before_main()函數前面,而析構也發生在after_main()前面.

 

 

__attribute__機制介紹

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,deprecatedmay_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/

 

 

offsetof與container_of宏[總結]

1、前言

  今天在看代碼時,遇到offsetof和container_of兩個宏,覺得很有意思,功能很強大。offsetof是用來判斷結構體中成員的偏移位置,container_of宏用來根據成員的地址來獲取結構體的地址。兩個宏設計的很巧妙,值得學習。linux內核中有着兩個宏的定義,並在鏈表結構中得到應用。不得不提一下linux內核中的鏈表,設計的如此之妙,只需要兩個指針就搞定了。后續認真研究一下這個鏈表結構。

2、offsetof宏

  使用offsetof宏需要包含stddef.h頭文件,實例可以參考:http://www.cplusplus.com/reference/cstddef/offsetof/

      offsetof宏的定義如下:

#define offsetof(type, member) (size_t)&(((type*)0)->member)

  巧妙之處在於將地址0強制轉換為type類型的指針,從而定位到member在結構體中偏移位置。編譯器認為0是一個有效的地址,從而認為0是type指針的起始地址。

3、container_of宏

  使用container_of宏需要包含linux/kernel.h頭文件,container_of宏的定義如下所示:

#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) 

container_of宏分為兩部分,

第一部分:const typeof( ((type *)0)->member ) *__mptr = (ptr);

通過typeof定義一個member指針類型的指針變量__mptr,(即__mptr是指向member類型的指針),並將__mptr賦值為ptr。

第二部分: (type *)( (char *)__mptr - offsetof(type,member) ),通過offsetof宏計算出member在type中的偏移,然后用member的實際地址__mptr減去偏移,得到type的起始地址,即指向type類型的指針。

第一部分的目的是為了將統一轉換為member類型指針。

4、測試程序

復制代碼
 1 #include <stdio.h>  2 #include <stdlib.h>  3  4 #define NAME_STR_LEN 32  5  6 #define offsetof(type, member) (size_t)&(((type*)0)->member)  7  8 #define container_of(ptr, type, member) ({ \  9 const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 10 (type *)( (char *)__mptr - offsetof(type,member) );}) 11 12 typedef struct student_info 13 { 14 int id; 15 char name[NAME_STR_LEN]; 16 int age; 17 }student_info; 18 19 20 int main() 21 { 22 size_t off_set = 0; 23 off_set = offsetof(student_info, id); 24 printf("id offset: %u\n",off_set); 25 off_set = offsetof(student_info, name); 26 printf("name offset: %u\n",off_set); 27 off_set = offsetof(student_info, age); 28 printf("age offset: %u\n",off_set); 29 student_info *stu = (student_info *)malloc(sizeof(student_info)); 30 stu->age = 10; 31 student_info *ptr = container_of(&(stu->age), student_info, age); 32 printf("age:%d\n", ptr->age); 33 printf("stu address:%p\n", stu); 34 printf("ptr address:%p\n", ptr); 35 return 0; 36 }
復制代碼

測試結果:

5、參考網址

http://blog.csdn.net/thomas_nuaa/article/details/3542572

http://blog.chinaunix.net/uid-28489159-id-3549971.html

 

 

C語言中offsetof宏的應用

offsetof  :
    Retrieves the offset of a member from the beginning of its parent structure.

size_t offsetof(structName, memberName);

Parameters:
    structName : Name of the parent data structure.
    memberName :Name of the member in the parent data structure for which to determine the offset.

Return Value : offsetof returns the offset in bytes of the specified member from
          the beginning of its parent data structure. It is undefined for bit fields.
Remarks :
    The offsetof macro returns the offset in bytes of memberName from the beginning of the structure specified by structName. You can specify types with the struct keyword.

Note  :
    offsetof is not a function and cannot be described using a C prototype.

 

 #define offsetof(s, m)   (size_t)&(((s *)0)->m)

s是一個結構名,它有一個名為m的成員(s和m 是宏offsetof的形參,它實際是返回結構s的成員m的偏移地址.

(s *)0 是騙編譯器說有一個指向類(或結構)s的指針,其地址值0

&((s *)0)->m   是要取得類s中成員變量m的地址. 因基址為0,這時m的地址當然就是m在s中的偏移

最后轉換size_t 型,即unsigned int。

有例子如:
struct   A
{
    int   i;
    int   j;
};
  
struct   A   *pA;
pA = new   A;
這時,pA實際上是一個Pointer, 指向某一確定的內存地址, 如0x1234;
而pA->i 整體是一個int型變量,其地址是&(pA->i), '&'為取址運算符;
那么&(pA->i)一定等於0x1234,因 i 是結構體A的第一個元素。
而&(pA->j)一定是0x1234 + 0x4 = 0x1238; 因為sizeof(int) = 4;
  
這個做法的巧妙之處就是:它把“0”作為上例中的pA,那么&(pA->j)就是 j 的offset

解析結果是:
(s*)0,將 0 強制轉換為Pointer to "s"   
可以記 pS = (s*)0,pS是指向s的指針,它的值是0;
那么pS->m就是m這個元素了,而&(pS->m)就是m的地址,就是offset

下面是個offsetof應用的例子,其中宏OBJECT_HEAD_ADDRES的作用是根據一個對象或結構的某成員的地址,求其首地址。

 

完整的例子如下:

/* offsetof example */

#include "stdafx.h"
#include <stdio.h>
#include <stddef.h>

/************************************************************************/
/* Macro OBJECT_HEAD_ADDRESS : calculate the struct's address according
/* to one of its member's address
/************************************************************************/
#define OBJECT_HEAD_ADDRESS(ClassName,MemberName,Addre) /
Addre - offsetof(ClassName, MemberName)

struct S
{
int ID;
char *addr;
char name[20];
};

struct S1
{
S employee;
char *title;
};

struct S2
{
char singlechar;
int arraymember[10];
char anotherchar;
};

int main(int argc, char* argv[])
{
/* Example 1
S2 s2 = {'a', 10, 'b'};
int head = int(&(s2.singlechar));
int memb = int(&(s2.anotherchar));

int i = OBJECT_HEAD_ADDRESS(S2, anotherchar, memb);

printf ("offsetof(S2, singlechar) is %d/n", offsetof(S2, singlechar));
printf ("offsetof(S2, arraymember) is %d/n", offsetof(S2, arraymember));
printf ("offsetof(S2, anotherchar) is %d/n", offsetof(S2, anotherchar));
printf("s2.anotherchar's address is %x ==> s2's address is %x/n", memb, i);
*/
/* Example 2
S s = {100, "Nanjing", "Thomas"};
int id = int(&(s.ID));
int ad = int(&(s.addr));
int nm = int(&(s.name));

int j = OBJECT_HEAD_ADDRESS(S, addr, ad);

printf ("offsetof(S, ID) is %d/n", offsetof(S, ID));
printf ("offsetof(S, addr) is %d/n", offsetof(S, addr));
printf ("offsetof(S, name) is %d/n", offsetof(S, name));
printf ("s.ID's address : %x/ns.addr's address : %x/ns.name's address : %x/n", id, ad, nm);
printf("s.addr's address is %x ==> s's address is %x/n", ad, j);
*/
/* Example 3 */
S1 s1 = {{100, "Nanjing", "Thomas Chen"},"Thomas Chen"};
int td = int(&(s1.title));
int k = OBJECT_HEAD_ADDRESS(S1, title, td);

printf ("offsetof(S1, employee) is %d/n", offsetof(S1, employee));
printf ("offsetof(S1, title) is %d/n", offsetof(S1, title));
printf("s1.title's address is %x ==> s1's address is %x/n", td, k);

return 0;
}
/* Example 1 Output */
offsetof(S2, singlechar) is 0
offsetof(S2, arraymember) is 4
offsetof(S2, anotherchar) is 44
s2.anotherchar's address is 12ff7c ==> s2's address is 12ff50

/* Example 2 Output */
offsetof(S, ID) is 0
offsetof(S, addr) is 4
offsetof(S, name) is 8
s.ID's address : 12ff64
s.addr's address : 12ff68
s.name's address : 12ff6c
s.addr's address is 12ff68 ==> s's address is 12ff64

/* Example 3 Output */
offsetof(S1, employee) is 0
offsetof(S1, title) is 28
s1.title's address is 12ff7c ==> s1's address is 12ff60

 

 

 

Linux內核container_of詳解

在開發Linux驅動的時候,經常會用到container_of這個宏:
include/linux/kernel.h
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  1. /**
  2.  * container_of - 通過結構體的一個成員獲取容器結構體的指針
  3.  * @ptr: 指向成員的指針。
  4.  * @type: 成員所嵌入的容器結構體類型。
  5.  * @member: 結構體中的成員名。
  6.  *
  7.  */
  8. #define container_of(ptr, type, member) ({ \
  9.     const typeof( ((type *)0)->member ) *__mptr = (ptr); \
  10.     (type *)( (char *)__mptr - offsetof(type,member) );})
 
    這個宏的作用其實很簡單,就是 通過一個容器(結構體)中某個成員的指針得到指向這個容器(結構體)的指針,簡單的說就是通過成員找容器。
 
這個宏的實現代碼也就只有兩行,下面仔細分析一下:
(1)const typeof( ((type *)0)->member ) *__mptr = (ptr);
定義一個中間變量__mptr,它等於提供給宏的參數ptr,也就是指向某個成員的指針。這個中間變量的命名意義是:
"__"代表內部使用,內核編程中常常這么做;
“m”代表middle。
 
 
(2)(type *)( (char *)__mptr - offsetof(type,member) );
這行代碼的作用是通過中間變量__mptr(指向某個成員的指針)減去這個成員在容器(結構體)中的偏移來得到指向容器(結構體)的指針。
這里注意偏移的獲取offsetof宏的實現:
 
include/linux/stddef.h
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  1. #ifdef __compiler_offsetof
  2. #define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
  3. #else
  4. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
  5. #endif
    這個實現依賴編譯器,如果編譯器內建了__compiler_offsetof(TYPE,MEMBER)的實現,就使用編譯器的實現,否則就是用((size_t) &((TYPE *)0)->MEMBER)。
    在Linux-2.6.29(含)以上內核必須使用4.1.1(不含)以上的編譯器,所以Linux-3.0內核肯定是使用了編譯器內核內建的宏實現
 
include/linux/compiler-gcc4.h
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  1. #define __compiler_offsetof(a,b) __builtin_offsetof(a,b)
---------------------------------------------------------------------
宏分析完了,這里附上一個圖來加深下印象:
 
紅色代表輸出,藍色代表輸入,黑色代表中間變量

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM