offsetof與container_of巨集分析

Zhaoxi_Zhang發表於2018-12-11

offsetof巨集:結構體成員相對結構體的偏移位置 container_of:根據結構體成員的地址來獲取結構體的地址

offsetof 巨集

原型:

#define offsetof(TYPE, MEMBER)	((size_t)&((TYPE *)0)->MEMBER)
複製程式碼

(TYPE *)0非常巧妙,告訴編譯器有一個指向結構體 TYPE 的指標,其地址是0,然後取該指標的 MEMBER 地址 &((TYPE *)0)->MEMBER,因為基址是0,所以這時獲取到的 MEMBER 的地址就是相當於在結構體 TYPE 中的偏移量了。 Example:

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>

struct TYPE{
    int mem;
    int member;
};

int main()
{
    struct TYPE type;
    printf("&type = %p\n", &type);
    printf("&type.member = %p\n", &type.member);
    printf("&((struct type *)0)->member = %lu\n", ((size_t)&((struct TYPE *)0)->member) );
    printf("offsetof(struct TYPE member) = %zd\n", offsetof(struct TYPE, member));
    return 0;
}
/*
result:
&type = 0x7ffc1104a110
&type.member = 0x7ffc1104a114
&((struct type *)0)->member = 4
offsetof(struct TYPE member) = 4
*/
複製程式碼

container_of 巨集

原型:linux-4.18.5

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:	the pointer to the member.
 * @type:	the type of the container struct this is embedded in.
 * @member:	the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({				\
	void *__mptr = (void *)(ptr);					\
	BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&	\
			 !__same_type(*(ptr), void),			\
			 "pointer type mismatch in container_of()");	\
	((type *)(__mptr - offsetof(type, member))); })
複製程式碼

網上所見更多是底下這個版本:

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

第一部分:void *__mptr = (void *)(ptr);const typeof( ((type *)0)->member ) *__mptr = (ptr); 兩個的差別在於 __mptr 的型別一個是 void * ,一個是 type *。 void * 較為容易理解,下面來看看 type *: 關於 typeof 關鍵字其作用是返回變數的型別,簡單理解就是如下,詳細可參見GCC typeof在kernel中的使用——C語言的“編譯時多型”

int a;
typeof(a) b; //這等同於int b;
typeof(&a) c; //這等同於int* c;
複製程式碼

因此const typeof( ((type *)0)->member ) *__mptr = (ptr);的作用就是通過 typeof 獲取結構體成員 member 的型別,然後定義一個這個型別的指標變數 __mptr 並將其賦值為 ptr。 第二部分:(type *)( (char *)__mptr - offsetof(type,member) ),通過offsetof巨集計算出 member 在 type 中的偏移,然後用 member 的實際地址__mptr減去偏移,得到 type 的起始地址。從上面關於offsetof巨集的 Example 也可以驗證這一點: &type.member = 0x7ffc1104a114 - &((struct type *)0)->member = 4 = &type = 0x7ffc1104a110

相關文章