これは、C99 で「柔軟な配列メンバー」に変換された、C99 以前の古い「構造体ハック」の例です。文字列を格納するとします。以下を使用して、スペースを動的に割り当てることができます。
struct Entry *make_entry(const char *str)
{
size_t len = strlen(str) + 1;
struct Entry *e = malloc(sizeof(struct Entry) + len);
if (e != 0)
{
e->bufferLen = len;
strcpy(e->buffer, str);
}
return e;
}
C99 では、同じコードを記述します。ただし、配列のサイズを指定しないため、構造体の宣言は異なります。
struct Entry
{
size_t bufferLen;
unsigned char buffer[];
};
これにより、特にsizeof(size_t) == 8
.
このような構造体 (柔軟な配列メンバー スタイルと構造体ハック スタイルの両方) には大きな制限があり、そのうちの 1 つは、構造体を単純に割り当てることができないことです。memmove()
余分なスペースを考慮して、またはでコピーする必要がありmemcpy()
ます。
struct Entry *dup_entry(const struct Entry *e)
{
struct Entry *r = malloc(sizeof(struct Entry) + e->bufferLen);
if (r != 0)
memmove(r, e, sizeof(struct Entry) + e->bufferLen);
return r;
}
また、そのような構造体の配列を作成することもできません (ただし、そのような構造体へのポインターの配列は作成できます)。
あなたの関数では、すでに1つの文字が格納さmake_entry()
れているため、メモリを1つ少なく割り当てることができると思います。Entry->buffer
興味深いコメント — そして控えめな表現。かつて別の回答があり(現在は削除されています)、興味深い適切なコメントがいくつか収集されました。これをこの回答に取り入れようとしています。
マイケル・バーは次のように述べています。
memcpy(pDst, pEntry, offsetof(struct Entry, buffer[pEntry->bufferLen]))
のほうが安全なイディオムです — 構造体の最後にある配列ハックの前に複数のエントリがある場合、それらすべてを手動で説明する必要はありません。アレイを自動的にハックします。同様に、インスタンスの割り当ては次のようになります。pEntry = malloc(offsetof(struct Entry, buffer[desiredVarArrayElements]))
これは興味深い観察であり、あなたが示唆していることと非常に一致しています。をこのように使用すると、offsetof()
コンパイル時の定数ではない結果が生じます。結果は、実行時に添え字として使用される値によって異なります。それは正統ではありませんが、私には何も悪いことがわかりません。添え字として使用されるサイズがヌル バイトをカウントに含めることが重要です。
また、この手法を短い文字列に使用すると、構造体に割り当てるバイト数が実際よりも少なくなる可能性があるという興味深い観察結果にもつながりますsizeof(struct Entry)
。これは、構造全体がほとんどの 32 ビット マシンで 4 バイト アラインされ、ほとんどのマシンで 8 バイト アラインされるためsizeof(size_t) == 8
です (通常は 64 ビット Unix システムで、64 ビット Windows ではありません)。
したがって、割り当てられる文字列が 3 バイト (2 文字と 1 つのヌル ターミネータ バイト) のみである場合、 に指定されるmalloc()
サイズは、構造サイズの8または16。
したがって、「struct hack」構造 (質問のように) 用に書いたコードは、(安全な) ショートカットを使用し、必要なメモリの合計をおそらく 4 バイトまたは 8 バイト分過剰に割り当てます。その過剰な割り当てを減らすことは可能でしょう。メモリ アロケータ自体は通常、切り上げられたサイズを割り当てることに注意してください。多くの場合 (必ずしもそうとは限りません)、32 ビット システムでは 8 バイトの倍数、64 ビット システムでは 16 バイトの倍数です。これは、より少ないバイトを割り当てようとしても、期待したほどのメリットがない可能性があることを意味します (ただし、場合によってはメリットがあります)。
C99 フレキシブル配列メンバーは、無駄なスペースをまったく発生させないことに注意してください。構造体のサイズには、柔軟な配列メンバーはまったく含まれていません。これにより、構造体ハックよりも使いやすくなります。同じように無駄なスペースを心配する必要はありません。
スティーブ・ジェソップは次のように述べています。
そのような構造体をコピー可能にする場合は、いずれにせよ、おそらく構造体に容量とサイズのフィールドが必要です。次に、容量をチェックし、サイズをコピーし(容量ではなく)、データをコピーするコピー機能を作成します。これら 3 つのことを別々に行う場合は、レイアウトを気にする必要はありません。また、やるべきことが非常に多いため、構造体のユーザーは自分でコピーしようとするのではなく、関数を使用する必要があります。
もちろん、これがまさに私がdup_entry()
;を定義した理由です。人々がコピーするときに馬鹿にならないようにすることは確かに必要です。make_entry()
と関数はdup_entry()
互いに一致するように実装する必要がありますが、それは難しいことではありません。