厳密な C を使用してこれを行うことができますが、標準に確実に準拠するには、特定の予防措置を講じる必要があります。以下で説明しますが、注意すべき点は次のとおりです。
(0) 次の宣言を含めて、パディングがないことを確認します。
extern int CompileTimeAssert[
sizeof(some_struct) == NumberOfMembers * sizeof(char *) ? 1 : -1];
(1) メンバーのアドレスではなく、構造体のアドレスからポインターを初期化します。
char **p = (char **) (char *) &struct1;
(上記は必要ないと思いますが、C標準からさらに推論を挿入する必要があります。)
++
(2) ポインタを使用または追加する代わりに、次の方法でポインタをインクリメントします。
p = (char **) ((char *) p + sizeof(char *));
ここに説明があります。
(0) の宣言は、コンパイル時のアサーションとして機能します。構造体にパディングがない場合、構造体のサイズは、メンバーの数にメンバーのサイズを掛けた値に等しくなります。次に、三項演算子が 1 に評価され、宣言が有効になり、コンパイラが処理を続行します。パディングがある場合、サイズは等しくなく、三項演算子は -1 に評価され、配列は負のサイズを持つことができないため、宣言は無効になります。その後、コンパイラはエラーを報告して終了します。
したがって、この宣言を含むプログラムは、構造体にパディングがない場合にのみコンパイルされます。さらに、宣言はスペースを消費せず (定義されていない配列のみを宣言します)、他の式 (条件が true の場合に配列サイズ 1 に評価される) で繰り返すことができるため、異なるアサーションが発生する可能性があります。同じアレイ名でテストされています。
項目 (1) と (2) は、通常、ポインター演算が配列内でのみ動作することが保証されている (末尾の概念的な番兵要素を含む) という問題に対処しています (C 2011 6.5.6 8 による)。ただし、C 標準では、C 2011 6.3.2.3 7 で、文字型に対して特別な保証が行われます。 . オブジェクトのサイズまで結果を連続的にインクリメントすると、オブジェクトの残りのバイトへのポインタが生成されます。
(1) では、C 2011 6.3.2.3 7 からわかりますが、これ(char *) &struct1
は の最初のバイトへのポインターですstruct1
。に変換されるとき(char **)
、それは の最初のメンバーへのポインターでなければなりませんstruct1
(特に、C 2011 6.5.9 6 のおかげで、同じポインターが異なる型であっても同じオブジェクトを指すことが保証されます)。
最後に、(2) は、配列演算がポインターで動作することが直接保証されていないという事実を回避します。つまり、厳密には配列内にないポインターをインクリメントp++
するため、6.5.6 8 では算術演算は保証されません。char *
それを 4 回繰り返して に戻しchar **
ます。パディングがないため、これは次のメンバーへのポインターを生成する必要があります。
(たとえば 4)のサイズを追加することはchar **
、 one の 4 つのインクリメントと同じではないと主張する人もいるかもしれませんchar
が、確かに、標準の意図は、合理的な方法でオブジェクトのバイトをアドレス指定できるようにすることです。ただし、この批判さえも避けたい場合は、(サイズが 4 の実装では) または(サイズが 8 の場合) に変更+ sizeof(char *)
できます。+1+1+1+1
+1+1+1+1+1+1+1+1