38

ANSI C では、offsetof は次のように定義されています。

#define offsetof(st, m) \
    ((size_t) ( (char *)&((st *)(0))->m - (char *)0 ))

NULL ポインターを逆参照しているのに、なぜセグメンテーション違反がスローされないのでしょうか? それとも、オフセットのアドレスのみが取り出されていることがわかり、実際に逆参照せずにアドレスを静的に計算する、ある種のコンパイラハックですか? また、このコードは移植可能ですか?

4

8 に答える 8

39

上記のコードでは、逆参照されている箇所はありません。*または->がアドレス値で使用され、参照された値を見つけると、逆参照が発生します。上記の唯一の使用は*、キャストを目的とした型宣言です。

->演算子は上記で使用されていますが、値へのアクセスには使用されていません。代わりに、値のアドレスを取得するために使用されます。これは、マクロ以外のコードのサンプルです。

SomeType *pSomeType = GetTheValue();
int* pMember = &(pSomeType->SomeIntMember);

2 行目は、実際には逆参照を引き起こしません (実装に依存します)。SomeIntMember値内のアドレスを返すだけpSomeTypeです。

ご覧のとおり、任意の型と char ポインターの間で多くのキャストが行われています。char の理由は、C89 標準で明示的なサイズを持つ唯一の (おそらく唯一の) 型の 1 つだからです。サイズは 1 です。サイズが 1 であることを確認することにより、上記のコードは、値の実際のオフセットを計算するという邪悪な魔法を実行できます。

于 2009-04-03T13:45:52.057 に答える
10

これは の典型的な実装ですがoffsetof、標準では義務付けられていません。

次のタイプとマクロは、標準ヘッダーで定義されています<stddef.h>[...]

offsetof(type,member-designator)

これは、型 を持つ整数定数式に展開されます。その値は、構造体の先頭( でsize_t指定) から構造体メンバー ( で指定) までのオフセット (バイト単位) です。型とメンバー指定子は、指定されたものでなければなりませんmember-designatortype

statictypet;

次に、式はアドレス定数に評価されます。(指定されたメンバーがビットフィールドの場合、動作は未定義です。)&(t.member-designator)

PJ Plauger の "The Standard C Library" を読んで、それについての議論と、<stddef.h>適切な言語にある可能性がある (あるべきか?) すべてのボーダーライン機能であり、特別なコンパイラ サポートが必要になる可能性があるその他の項目を参照してください。

それは歴史的な関心だけですが、私は 386/IX で初期の ANSI C コンパイラを使用しました (1990 年頃の歴史的な関心を参照してください) offsetof

#define offsetof(st, m) ((size_t)((char *)&((st *)(1024))->m - (char *)1024))

これはある種のコンパイラのバグでした。特に、ヘッダーがコンパイラと共に配布され、機能しなかったためです。

于 2009-04-03T14:39:38.777 に答える
8

質問の最後の部分に答えるために、コードは移植可能ではありません。

2 つのポインターを減算した結果は、2 つのポインターが同じ配列内のオブジェクトを指しているか、配列の最後のオブジェクトの 1 つ後ろを指している場合にのみ定義され、移植可能です (7.6.2 加算演算子、H&S 第 5 版)。

于 2009-04-03T14:37:26.927 に答える
2

mタイプのオブジェクトの表現の開始アドレスを基準にしたメンバーのオフセットを計算しますst

((st *)(0))NULLタイプのポインタを参照しますst *&((st *)(0))->mこのオブジェクトのメンバーmのアドレスを参照します。このオブジェクトの開始アドレスは0 (NULL)であるため、メンバーmのアドレスは正確にオフセットです。

char *変換と差は、オフセットをバイト単位で計算します。ポインタ操作によると、タイプの2つのポインタを区別すると、結果は、オペランドに含まれる2つのアドレスの間に表されるT *タイプのオブジェクトの数になります。T

于 2009-04-03T13:55:49.490 に答える
2

リスト 1:offsetof()マクロ定義の代表的なセット

// Keil 8051 compiler
#define offsetof(s,m) (size_t)&(((s *)0)->m)

// Microsoft x86 compiler (version 7)
#define offsetof(s,m) (size_t)(unsigned long)&(((s *)0)->m)

// Diab Coldfire compiler
#define offsetof(s,memb) ((size_t)((char *)&((s *)0)->memb-(char *)0))

typedef struct 
{
    int     i;
    float   f;
    char    c;
} SFOO;

int main(void)
{
  printf("Offset of 'f' is %zu\n", offsetof(SFOO, f));
}

マクロ内のさまざまな演算子は、次の手順が実行される順序で評価されます。

  1. ((s *)0)は整数ゼロを取り、それを へのポインターとしてキャストしますs
  2. ((s *)0)->m構造体 member を指すポインターを逆参照しますm
  3. &(((s *)0)->m)のアドレスを計算しますm
  4. (size_t)&(((s *)0)->m)結果を適切なデータ型にキャストします。

定義により、構造体自体はアドレス 0 に存在します。したがって、(上記のステップ 3) が指すフィールドのアドレスは、構造体の先頭からのオフセット (バイト単位) でなければなりません。

于 2014-07-23T17:39:14.427 に答える
2

逆参照していないため、セグメンテーション違反はありません。ポインター アドレスは、メモリ操作のアドレス指定には使用されず、別の数値から減算される数値として使用されています。

于 2009-04-03T13:47:58.730 に答える