構造体メンバーのアドレスから構造体変数のアドレスを取得する

2801 ワード

C言語の構造体は、異なるタイプのオブジェクトを1つのオブジェクトに集約することができます.メモリでは、コンパイラはメンバーのリスト順に各構造体変数のメンバーにメモリを割り当てますが、Cのメモリ整列メカニズムと異なるマシン間の違いにより、各メンバーの間にギャップがある可能性があります.したがって、他のメンバーまたは構造体オブジェクトのアドレスは、メンバータイプが占める文字長によって簡単に推定することはできません.
構造体のヘッダアドレスに対するメンバーのオフセット量を計算する場合、一般的に最初の反応は、メンバーのアドレスと構造体オブジェクトのヘッダアドレスとのバイト数です.たとえば、構造体タイプを定義しました.
typedef struct list_node {
	int ivar;
	char cvar;
	double dvar;
	struct list_node *next;
} list_node;

このタイプで変数を定義します:list_node ln; 今lnを求めるとします.dvarのアドレスとlnのアドレスの間に何バイトの差があるか、この式で:(char*)&ln.dvar-(char*)&lnで求めることができるが,この式の原理は,&符でlnを取ることである.dvarとlnのアドレスは、強制的にchar*型に変換し、両者を減算します.char*型に変換するのは、char型変数が1バイトを占めるためです.ポインタ減算の特性に基づいて、両者の間のバイト数を要求します.もちろん、単一のバイト単位です.では、オフセット量を求める基本原理はこれです.問題が来ました.もしあなたが関数を呼び出すとき、パラメータだけで構造体のメンバーに伝わり、関数には構造体のオブジェクト自体がありません.このような方法は役に立たないとしたら、どうすればいいですか.別の角度から考えて、B-Aの差がオフセット量であるとすれば、Aを0に等しくすれば、B自体の値はオフセット量ではないか.この考え方に基づいて、偉大なLinuxカーネルがどのようにオフセット量を求める操作を実現しているかを見てみましょう.
/*
 *    linux-2.6.7     
 * filename: linux-2.6.7/include/linux/stddef.h
 */
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

linuxでは1つのマクロoffsetofが定義されています.このマクロは2つのパラメータを受け入れています.1つのTYPEは構造体のタイプを表し、もう1つのMEMBERは構造体のメンバーを表しています.後のマクロ置換部分で何が起こったかを見てみましょう.まず((TYPE*)0)という部分を見て、0という数字を強制的にTYPE*型のポインタタイプに変換します.このように(TYPE*)0)この全体は1つのポインタが0というアドレスを指していることに相当し、0というアドレスが合法かどうかにかかわらず、本当にこのような構造体オブジェクトがあるかどうかにかかわらず、0アドレスをはじめとする連続メモリを1つの構造体オブジェクトとして操作するので、(TYPE*)0->MEMBERという部分を見てみましょう.((TYPE*)0)このポインタは構造体オブジェクトの中のMEMBERメンバーを取ろうとしていますが、これはメモリを読み取る操作だけでデータを書き込んでいないので、アドレスが合法ではないとはいえ、セグメントエラーは発生しません.このようにMEMBERメンバーを取ると、前の&符がMEMBERメンバーにアドレスを取ることができます.さっき私も言いましたが、B-Aの差はオフセット量で、Aが0なら、B自体がオフセット量であり、それが現在の状況にちょうど対応しており、(TYPE*)0自体が0アドレスをはじめとして動作しているので、その取り出されたMEMBERメンバーのアドレスが構造体ヘッダアドレスに対するオフセット量であり、このアドレスを強制的にsize_に変換するtタイプで、そのメンバーのオフセット量が得られます.いい感じがしますか.
メンバーのオフセット量を知っていれば、構造体オブジェクト自体のアドレスを求めるのは簡単ではないでしょうか.メンバーのアドレスからオフセット量を差し引くのは構造体オブジェクトの最初のアドレスではないでしょうか.この考え方に基づいて、偉大なLinuxがどのように実現されているかを見てみましょう.
/*
 *    linux-2.6.7     
 * filename: linux-2.6.7/include/linux/stddef.h
 */
#define container_of(ptr, type, member) ({			\
        const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
        (type *)( (char *)__mptr - offsetof(type,member) );})
同様に定義されたマクロであり、このマクロは3つのパラメータを受け入れ、最初のptrは既知のメンバーのアドレスを表し、2番目のtypeは構造体のタイプを表し、3番目のmemberは既知のメンバーを表し、マクロの置換部分ではtypeofキーワードを使用します.このキーワードの使い方が分からない場合は、私のブログを参照してください:GNU C規格のtypeofキーワードを詳しく理解し、(type*)0)->memberこの部分は上記と同様に、0アドレスを仮定した構造体オブジェクトのmemberメンバーを取得し、typeofでmemberメンバーのタイプを取得し、このタイプで対応するポインタ変数__を定義します.mptrをptrとして割り当て、その後((char*))mptr-offsetof(type,member))この部分はmptrは強制的にchar*型に変換され,さらにmemberメンバーのオフセット量を減算してその構造体オブジェクトのヘッダアドレスが得られ,このヘッダアドレスを強制的にtype*型,すなわち構造体自体のタイプに対応するポインタタイプに変換され,最終的にその構造体オブジェクトのアドレスが求められる.