container_ofマクロとは

container_ofマクロは、Linux Kernelで用いられ、「構造体メンバポインタ」から「そのメンバを含む構造体の先頭ポインタ」を得られるマクロです。C言語では、offsetofマクロによって構造体メンバアドレスのオフセット(構造体先頭アドレスからメンバ変数までのオフセット)を算出できます。container_ofマクロは、このオフセットを利用し、構造体の先頭アドレスを算出しています。

本記事では、container_ofマクロに関して、以下の内容を解説します。

解説内容

  • container_ofマクロの定義
  • 引数
  • 使用例(User空間での使用例)
  • 実装解説

container_ofマクロの定義

container_ofマクロは、$(KERNEL_TOP_DIR)/include/linux/kernel.hに定義されています。

/**                                                                           
 * 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) );})                     

引数

No.引数名説明
1ptr構造体メンバへのポインタ
2type第一引数ptrが指す構造体メンバを含む構造体名
3member第一引数ptrが指す構造体メンバの名称

使用例(User空間での使用例)

User空間でもcontainer_ofマクロは動作するため、今回の使用例ではUser空間のサンプルプログラムを用います。以下のサンプルプログラムは、game構造体のメンバ変数(price、genre、score)のポインタを用いて、game構造体の先頭ポインタを取得します。また、「メンバ変数アドレス」と「構造体の先頭アドレス」のオフセットも合わせて表示します。

以下、サンプルコードおよび実行例です。

#include <stdio.h>     
#include <stdlib.h>  
#include <string.h>
#include <stddef.h>                                             
                                                                                  
#define container_of(ptr, type, member) ({          \                             
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \                     
    (type *)( (char *)__mptr - offsetof(type,member) );})                         
                                                                                  
struct game {                                                                     
    int  price;                                                                   
    char genre[256];                                                              
    char score[128];                                                              
};                                                                                
                                                                                  
int main (void) {                                                                 
    struct game *g = NULL;                                                        
                                                                                  
    g = (struct game *)malloc(sizeof(struct game));                               
    memset(g, 0, sizeof(struct game));                                            
                                                                                  
    printf("Struct head address            = %p\n\n", g);                         
                                                                                  
    printf("Struct member price address    = %p\n", &g->price);                   
    printf("OFFSET:head <-> member price   = %p\n", offsetof(struct game, price));
    printf("Struct head address from price = %p\n\n",                             
            container_of(&g->price, struct game, price));                         
                                                                                  
    printf("Struct member genre address    = %p\n", g->genre);                    
    printf("OFFSET:head <-> member genre   = %p\n", offsetof(struct game, genre));
    printf("Struct head address from genre = %p\n\n",                             
            container_of(g->genre, struct game, genre));                          
                                                                                  
    printf("Struct member score address    = %p\n", g->score);                    
    printf("OFFSET:head <-> member score   = %p\n", offsetof(struct game, score));
    printf("Struct head address from score = %p\n",                               
            container_of(g->score, struct game, score));                          

    free(g); 
    g = NULL;

    return 0;
}                                                              
$ gcc -o test test.c

$ ./test 
Struct head address            = 0x55cf6e4c4260  (注釈):game構造体の先頭アドレス

 (注釈):メンバ変数priceは、構造体の先頭メンバのため、game構造体の先頭アドレスと元々一致しています。
Struct member price address    = 0x55cf6e4c4260  
OFFSET:head <-> member price   = (nil)
Struct head address from price = 0x55cf6e4c4260

Struct member genre address    = 0x55cf6e4c4264
OFFSET:head <-> member genre   = 0x4
Struct head address from genre = 0x55cf6e4c4260 (注釈):メンバ変数genreからgame構造体の先頭アドレスを取得

Struct member score address    = 0x55cf6e4c4364
OFFSET:head <-> member score   = 0x104
Struct head address from score = 0x55cf6e4c4260 (注釈):メンバ変数scoreからgame構造体の先頭アドレスを取得

実装解説

以下の通り、container_ofの定義を読みやすい形に整形しました。

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

まず、container_ofマクロは、変数__mptrに構造体メンバ変数のアドレスを代入しています。抑えるべきポイントは、以下の3点であり、重要なのは3点目(補足を後述)です。

ポイント

  • (type *)0)->memberは、変数memberを取得するため、便宜的にNULLポインタをキャストしています
  • typeof()は、引数で指定した変数の型を返します(gcc拡張構文)
  • 型を指定してポインタを得る理由は、コンパイル時に異常なアドレスを参照する事を防止したいから

上記のポイント3点目に関して、補足します。container_ofマクロは、本質的にはオフセット計算をしたいだけなため、char型によるポインタ計算で実装可能です。しかし、この方法では、containe_ofマクロ使用者の実装ミスによって、異常なアドレスを参照するバグが発生する可能性があります(例:メンバ変数の名称を間違えた場合など)。一方で、構造体メンバ型を用いてポインタアドレスを取得すれば、実装ミス時にコンパイルエラーが発生します。そのため、バグを未然に防げます。

以上を踏まえると、container_ofマクロ定義の__mptrに構造体メンバアドレスを代入する処理は、以下のように読み解けます。なお、前述のサンプルコードを前提として記載しています。

struct game {                                                  
    int  price;                                                
    char genre[256];                                           
    char score[128];                                           
};                                                             
container_of(&g->price, struct game, price); を前提として考えれば、

container_ofマクロ定義の__mptrに構造体メンバアドレスを代入する処理は、
const typeof( ((type *)0)->member ) *__mptr = (ptr); 
 ↓
const typeof( ((struct game *)0)->member ) *__mptr = (&g->price); 
 ↓
const  int *__mptr = (&g->price); 
と読み解けます。

続いて、container_ofマクロのオフセット計算部分 (char *)__mptr - offsetof(type,member)です。計算式は「構造体メンバアドレス - 構造体の先頭アドレスから構造体メンバ変数までのオフセット」です。前述のサンプルプログラム上でオフセットを表示しているため、その結果を用いて確認します。以下、サンプルプログラムの出力結果(一部)です。

Struct member score address    = 0x563ddd56f364  (注釈):メンバ変数scoreのアドレス
OFFSET:head <-> member score   = 0x104         (注釈):オフセット
Struct head address from score = 0x563ddd56f260  (注釈):構造体の先頭アドレス

手計算すれば、

「構造体メンバアドレス (0x563ddd56f364) - 構造体の先頭アドレスから構造体メンバ変数までのオフセット(0x104) = 構造体の先頭アドレス(0x563ddd56f260)」

となる事が分かります。以下にイメージ図を示します。

構造体の先頭:-------------------: 0x563ddd56f260       
          |  メンバ変数price    |      ↑               
          :-------------------:  offset:0x104
          |  メンバ変数genre    |      ↓               
          :-------------------: 0x563ddd56f364       
          |  メンバ変数score    |                      
          :-------------------: