10

構造体があり、オフセットをメンバーに抽出するとします。

struct A {
    int x;
};

size_t xoff = offsetof(A, x);

struct A標準に準拠した方法でメンバーを抽出するためのポインターが与えられた場合、どうすればよいですか? もちろん、正しいオフセットと正しいオフセットがあると仮定しstruct A*ます。1 つの試みは、次のようなことです。

int getint(struct A* base, size_t off) {
    return *(int*)((char*)base + off); 
}

これはおそらく機能しますが、たとえば、ポインターが同じ配列のポインター (または末尾を過ぎたポインター) である場合にのみ、ポインター演算が標準で定義されているように見えることに注意してください。これは事実である必要はありません。技術的には、その構造は未定義の動作に依存しているように見えます。

別のアプローチは

int getint(struct A* base, size_t off) {
    return *(int*)((uintptr_t)base + off);
}

これもおそらく機能intptr_tしますが、存在する必要はなく、私が知る限り、算術演算でintptr_t正しい結果が得られる必要はないことに注意してください(たとえば、一部のCPUには、バイト境界で整列されていないアドレスを処理する機能があることを思い出します。配列内のintptr_tそれぞれについて 8 ずつ増加することを示唆しています)。char

標準で忘れられているもの (または私が見逃したもの) があるようです。

4

2 に答える 2

3

C 標準、7.19共通定義<stddef.h>、パラグラフ 3 では、offsetof()次のように定義されています。

マクロは

NULL

これは、実装定義の null ポインター定数に展開されます。と

offsetof(*type*, *member-designator*)

これは、 type を持つ整数定数式に展開され ますsize_t。そのは、構造体の先頭 ( typeで指定) から構造体メンバー ( member-designator で指定)までのオフセット (バイト単位) です。

したがって、バイト単位offsetoff()でオフセットを返します。

また、6.2.6.1 一般、第 4 項には次のように記載されています。

他のオブジェクト タイプの非ビット フィールド オブジェクトに格納される値は、 n × CHAR_BITビットで構成されます。ここで、nはそのタイプのオブジェクトのサイズ (バイト単位) です。

CHAR_BITは a のビット数として定義されているため、characharは 1バイトです。

したがって、これは標準に従って正しいです。

int getint(struct A* base, size_t off) {
    return *(int*)((char*)base + off); 
}

baseこれは aに変換され、アドレスにバイトがchar *追加されます。が の結果であるoff場合、結果のアドレスは が指す内のアドレスです。offoffsetof(A, x);xstructure Abase

2番目の例:

int getint(struct A* base, size_t off) {
    return *(int*)((intptr_t)base + off);
}

intptr_t符号なし値と符号なし値を加算した結果に依存しますsize_t

于 2016-05-24T12:46:08.770 に答える
0

標準 (6.5.6) が配列のポインター演算のみを許可する理由は、配列要件を満たすために構造体にパディング バイトが含まれる場合があるためです。したがって、構造体内でポインター演算を行うことは、実際には正式には未定義の動作です。

実際には、自分が何をしているのかを知っている限り機能します。base + offそこに有効なデータがあり、適切にアクセスされていれば、位置合わせがずれていないことがわかっているため、失敗することはありません。

したがって(intptr_t)base + off、ポインター演算がなくなり、単純な整数演算だけになるため、実際にははるかに優れたコードになります。intptr_tは整数なのでポインタではありません。

コメントで指摘されているように、このタイプは存在することが保証されておらず、7.20.1.4/1 に従ってオプションです。移植性を最大限に高めるために、 orなど、存在保証されている他の型に切り替えることができると思います。ただし、サポートされていない C99/C11 コンパイラがまったく有用かどうかについては議論の余地があります。intmax_tptrdiff_tintptr_t

(ここには小さな型の問題があります。つまり、それintptr_tは符号付きの型であり、必ずしも と互換性があるわけではありません。暗黙的な型の昇格の問題が発生する可能性があります。可能であればsize_t使用する方が安全です。)uintptr_t

次の質問は、*(int*)((intptr_t)base + off)明確に定義された動作であるかどうかです。ポインター変換に関する標準の部分 (6.3.2.3) は、次のように述べています。

任意のポインター型を整数型に変換できます。前に指定された場合を除き、結果は実装定義です。結果が整数型で表現できない場合、動作は未定義です。結果は、任意の整数型の値の範囲内にある必要はありません。

この特定のケースでは、そこに が正しく配置されていることがわかっintているので、問題ありません。

(ポインターのエイリアシングに関する懸念も当てはまらないと思います。少なくとも、コンパイルしgcc -O3 -fstrict-aliasing -Wstrict-aliasing=2てもコードが壊れることはありません。)

于 2016-05-24T13:49:55.293 に答える