我對CONTAINING_RECORD宏的詳細解釋


tfref 

 宏CONTAINING_RECORD的用處其實還是相當大的, 而且很是方便, 它的主要作用是:
    根據結構體中的某成員的地址來推算出該結構體整體的地址!
  下面從一個簡單的例子開始說起:
  我們定義一個結構體, 同時類型化:

typedef struct{
    int a;
    int b;
    int c;
}ss;

  這是一個很簡單的結構體, 沒什么特殊的, 稍微分析下該結構體(假設在32位平台上):
    結構體的大小(字節):4+4+4=12字節
    成員a的偏移:0
    成員b的偏移:4
    成員c的偏移:8
  我們用ss來定義一個變量:
    ss s = {1,2,3};
  那么此時a,b,c的值分別為:a=1,b=2,c=3.
其實編譯器在生成代碼的時候其實是這樣給成員變量賦值的:
  假定s的地址為:0x12000000, 則:
    *(int*)((char*)&s + 0) = 1;
    *(int*)((char*)&s + 4) = 2;
    *(int*)((char*)&s + 8) = 3;
  也就是說是在&s的地址基礎上加上變量的偏移來確定成員的指針並賦值的, 所以:
    &s->a 將得到 0x12000000 + 0 = 0x12000000
    &s->b 將得到 0x12000000 + 4 = 0x12000004
    &s->c 將得到 0x12000000 + 8 = 0x12000008
  所以:    結構體的地址   +  成員變量的偏移 = 成員變量的地址
  
移一下項:  成員變量的地址 -  成員變量的偏移 = 結構體的地址
  哇哇, 這就是我們想要的地址, 不就是做了個減法嘛~~~囧

  首先, 成員變量的地址是我們知道的.
  其次, 我們需要得到成員變量的偏移(假定為成員b的偏移).
    怎么辦呢? 我們可以這樣:
      &s->b - (unsigned long)&s, 這樣就可以得到成員b的偏移了, 但是, 但是, &s 是我們需要的, 顯然暫時是個未知數, 既然這樣...
    那, 我們再做一次減法吧(非正確的C語言表達式, 不過結果沒問題, 這里只是顯得清楚點):
      &(s-s)->b - (unsinged long)&(s-s),
    其中, 為保證類型一致, 需要:
      s-s = (ss*)0
      (unsigned long)&(s-s) = (unsigned long)(ss*)0 = 0, 直接省略該部分就可以了
    那么, 化簡得到: &((ss*)0)->b - (unsigned long)0
    最簡結果: &((ss*)0)->b, 這就是b的偏移
  哈哈, 很簡單吧, 0指針的妙用, 總共做了兩次減法而已~ 對你們數學帝來說肯定不是問題啦~
  其中, 我們需要知道ss結構體的原型, ss結構體中的某個成員變量b(其實無論哪個都一樣, 只是要和前面提供指針的那個變量要一致)

  總結下, 我們需要提供:結構體中某個成員變量的地址, 該結構體的原型, 該結構體中的某個成員變量(與前面要是同一個變量)

  最終的CONTAINING_RECORD的定義為:

#define CONTAINING_RECORD(addr,type,field) ((type*)((unsigned char*)addr - (unsigned long)&((type*)0)->field))
  addr: 結構體中某個成員變量的地址
  type: 結構體的原型
  field: 結構體的某個成員(與前面相同)


  好了, 所有的結論都出來了, 這是一個萬能公式, 不管成員變量是哪一個結果都正確, 這是相對於知道第一個變量的地址而言的:
    如果知道的是第一個成員的地址(pa = &s->a)的話, 這是最簡單的情況了:
    直接強制類型轉換就可以了: (ss*)pa 即可, 此時 &((type*)0)->field 這部分恰好為0
    所以結果直接就是((type*)addr)了, 最簡單的情況. 也是我們最容易想到的一種情況, 比如把鏈表元素放在結構體的最開始 ~~~
  
  到這里這個CONTAINING_RECORD宏就已經說完了~  
  現在, 我們在使用LIST_ENTRY等雙向鏈表時, 不管把該鏈表放在結構體的哪個地方, 都可以在遍歷鏈表時通過CONTAINING_RECORD宏來准確得到整個結構體的地址了~
記得移除鏈表中的某個元素的時候, 要free整個結構體的地址才行哦, WDK提供的操作函數只是把該鏈表元素脫離整個鏈表~~~

btw:
  把addr轉換為 unsigned char*的原因是在指針計算時的計算單位為1, 也就是說 (unsigned char*)addr+1 = addr+1, 不轉換的話肯定是錯誤的
  把&((type*)0)->field轉換為(unsigned long)4個字節寬的同時是要保證表達式不是由兩個指針的算術操作構成的, 因為C語言標准未定義那樣的運算

寫了這么多, 希望沒落下什么吧~
女孩不哭(QQ:191035066)@2013-01-07 18:56:57 http://www.cnblogs.com/memset


免責聲明!

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



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