Linux Kernel: 構造体メンバポインタから構造体の先頭ポインタを得るcontainer_ofマクロ

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に定義されています。

                                              

引数

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

                                          

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

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

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

                                               

実装解説

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

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

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

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

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

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

手計算すれば、

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

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

                                        

おすすめ