Try   HackMD

【Linux】Linux核心原始程式碼巨集 container_of 以及 offsetof

簡介

這兩個巨集對於Linux核心原始程式碼非常的重要,它適用的範圍非常廣泛,被應用在Linked Listhash Table這一通用的資料結構中,用來化簡程式設計,並讓C語言也具備物件導向的行為,也是Linux物件導向中相當重要的機制!

offsetof 巨集

#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
  • 目的 : 取得結構體內成員相對於結構體起始位置的位移量
  • size_t : 表示一個byte的單位

操作細節

  • (TYPE *)0 : 將 0 (數值意義上)轉型成 type 型別的指標
  • &((TYPE *)0)->MEMBER : 取得其成員記憶體位址

編譯器處理 &((type *)0)->member 時,不會真正去存取地址為 0 的記憶體區段,只是將 0(數值意義上) 看做指標操作的一個運算元,除非額外有dereference操作。 https://hackmd.io/@sysprog/linux-macro-containerof

container_of 巨集

#ifndef container_of
/**
 * 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) ({			\
	const typeof(((type *)0)->member) * __mptr = (ptr);	\
	(type *)((char *)__mptr - offsetof(type, member)); })
#endif
  • 目的 : 給定結構體內某成員的指標,求得原結構體的記憶體位置
  • 應用 :
    • 模擬繼承與多態 : 常用於模擬 C 語言中的「繼承」,使子結構體可以引用到父結構體的屬性或方法。
    • 通用資料結構設計 : 雙向列表(doubly Linked List)哈希表(Hash-Table) 在Linux核心原始程式碼中採用這樣設計模式,使其能夠嵌入至任意資料結構中並進行操作

操作細節

  • 使用__typeof__ 取得 typemember 的資料型態,並且將 ptr 指派給 __pmember
  • __pmember 代表該 type 資料結構中成員的資料型態
  • 接著透過 offsetof(type, member) 取得 membertype 中的offset
  • 將絕對地址 (char *) __pmember 減去 offsetof(type, member) 即可得到結構體的起始位

轉型成 char * 目的是確保地址操作是以 1 byte 為基準,由於 char * 不論在哪裡都是1 byte(C語言標準),藉由這樣轉換方式能夠確保指標操作不會有問題

下面這張清楚地說明 container_of 的操作

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

可以透過contain_of設計以下的物件導向的概念

  • 封裝(encapsulation)
  • 繼承(inheritance)
  • 多型(polymorphism)

在C語言上可以透過函數指標將方法綁定在特定資料結構上,所以C語言是需要透過一些語法或巨集來達成封裝的效果,雖然有語法的限制,但並不影響物件導向的精神

物件導向是一種態度,語法只是輔助
Jserv

Example

#include <linux/kernel.h>
#include <stddef.h>
#include <stdio.h>

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


// struct data
// {
//     short a;
//     char b;
//     double c;
// }__attribute__((packed)); // align memory

struct data
{
    short a;
    char b;
    double c;
};

int main(int argc, char **argv)
{
    struct data x = {
        .a = 25,
        .b = 'A',
        .c = 12.45
    };
    char *p = (char *) &x;
    printf("a=%d\n", *((short *) p));
    p += sizeof(short);
    printf("b=%c\n", *((char *) p));
    p += sizeof(char);
    printf("c=%lf\n", *((double *) p));
    
    printf("\n*****use offsetof *****\n");
    p = (char *) &x;
    printf("a=%d\n", *((short *) p));
    
    p += offsetof(struct data, b);
    printf("b=%c\n", *((char *) p));

    p = (char *) &x;
    p += offsetof(struct data, c);
    printf("c=%lf\n", *((double *) p));
    return 0;
}

參考資料

  1. Linux 核心原始程式碼巨集: container_of