コンパイル時に、Cはほとんどの型情報を破棄し[1]、オフセットのみを残します。したがって、関数は疑似コードで次のようにコンパイルされます。
changeCount:
assign *(*(stack_ptr + offset_element) + offset_Count) + 1
to *(stack_ptr + offset_element) + offset_Count;
assign 1 to return_value;
pop
stack_ptrは、changeCountを呼び出したときに作成されたスタックフレームの場所です。offset_elementは、stack_ptrを基準にした要素引数の場所ですが、offset_Countとは何ですか。コンパイラがコードについて知っているのは、例で示したものだけであることに注意してください。elementはジェネリックポインタであり、実際には何かへのポインタではありません。変数[2]にキャストまたは割り当てることにより、どの要素が指しているかをコンパイラーに通知する必要があります。
typedef struct { int Count; } counted_t;
int changeCount(void* element)
{
counted_t* counted = element;
counted.Count++;
return 1;
}
この関数は、基本的に上記と同じ(擬似)コードを生成しますが、コンパイラーは、Countのオフセットがどうあるべきかを認識します。
あなたは、どの要素が指しているのかというタイプには3つの可能性があると述べています。これを処理する方法はいくつかあります。区別された結合または「継承された」構造のいずれかです。区別されたユニオンの使用については、たとえば、1つの要素が3つの可能性のどれを識別する列挙型であり、別の要素が3つの可能な構造のユニオンである構造。これは、ML言語(OCaml、Haskellなど)が代数的データ型と呼んでいるもの、またはPascalのユニオンとほぼ同じです。「継承」には、タイプ定義を使用できます。
typedef struct { counted_t counted; int i; } counted_int_t;
typedef struct { counted_t counted; double d; } counted_double_t;
typedef struct { counted_t counted; char* s; } counted_charptr_t;
この場合、上記のchangeCount関数を使用して、counted_int_t、counted_double_t、またはcounted_charptr_tへのポインターを渡すことができます。
何が起こるかというと、counted_t要素が最初である限り、コンパイラは同じ場所の「子孫」構造にCount要素を持つ3つの構造をレイアウトします。(少なくとも、これまでに使用したすべてのコンパイラーと、これまでに見たすべてのコードで、これはある時点でC標準になったと思いますが、これはごく普通のイディオムです。)
[1]デバッグ情報を除いて、コンパイラーにそれを発行するように指示した場合。ただし、プログラムはその情報にアクセスできないため、この場合は役に立ちません。
[2] x ++(postincrement)操作は、適用される変数(まあ、左辺値)をインクリメントします。元のコードでの割り当ては不要です。