私はいくつかの古いコードのリファクタリングに取り組んでおり、長さがゼロの配列を含む構造体をいくつか見つけました(以下)。もちろん、プラグマによって警告が抑制されましたが、そのような構造を含む「新しい」構造では作成できませんでした(エラー2233)。配列として 'byData'がポインターとして使用されていますが、代わりにポインターを使用しないのはなぜですか?または長さ1の配列?そしてもちろん、プロセスを楽しむためのコメントは追加されていません...そのようなものを使用する理由はありますか?それらをリファクタリングする際のアドバイスは?
struct someData
{
int nData;
BYTE byData[0];
}
注意:C++、Windows XP、VS 2003
はい、これはC-Hackです。
任意の長さの配列を作成するには:
struct someData* mallocSomeData(int size)
{
struct someData* result = (struct someData*)malloc(sizeof(struct someData) + size * sizeof(BYTE));
if (result)
{ result->nData = size;
}
return result;
}
これで、指定した長さの配列を持つsomeDataのオブジェクトができました。
残念ながら、構造体の最後に長さ0の配列を宣言する理由はいくつかあります。基本的には、APIから可変長構造を返す機能を提供します。
Raymond Chenはこの件に関して優れたブログ投稿を行いました。あなたが望む答えが含まれている可能性が高いので、この投稿をご覧になることをお勧めします。
彼の投稿で、0ではなく1のサイズの配列を扱っていることに注意してください。 これは、長さゼロの配列が標準へのより新しいエントリーであるためです。 彼の投稿はまだあなたの問題に当てはまるはずです。
http://blogs.msdn.com/oldnewthing/archive/2004/08/26/220873.aspx
[〜#〜]編集[〜#〜]
注:Raymondの投稿によると、長さ0の配列はC99では正当ですが、実際にはC99ではまだ正当ではありません。ここでは長さ0の配列の代わりに、長さ1の配列を使用する必要があります
これは、柔軟なサイズの配列を可能にする古いCハックです。
C99標準では、arr []構文をサポートしているため、これは必要ありません。
「サイズ1の配列を使用しない理由」に関する直感は、適切です。
長さがゼロの配列の宣言は制約違反であるため、コードは「C struct hack」を間違って実行しています。これは、コンパイラがコンパイル時にすぐにハックを拒否し、翻訳を停止する診断メッセージを表示することを意味します。
ハックを実行したい場合は、コンパイラを越えてこっそり忍ばなければなりません。
「C struct hack」(1989 ANSI Cに遡るC言語の方言と互換性があり、おそらくそれ以前のバージョンと互換性があります)を行う正しい方法は、サイズ1の完全に有効な配列を使用することです。
_struct someData
{
int nData;
unsigned char byData[1];
}
_
さらに、_sizeof struct someData
_の代わりに、byData
の前の部分のサイズが次のように計算されます。
_offsetof(struct someData, byData);
_
byData
に42バイトのスペースを持つ_struct someData
_を割り当てるには、次のようにします。
_struct someData *psd = (struct someData *) malloc(offsetof(struct someData, byData) + 42);
_
このoffsetof
の計算は、配列サイズがゼロの場合でも実際には正しい計算であることに注意してください。ご覧のとおり、sizeof
構造全体にパディングを含めることができます。たとえば、次のような場合:
_struct hack {
unsigned long ul;
char c;
char foo[0]; /* assuming our compiler accepts this nonsense */
};
_
_struct hack
_のサイズは、ul
メンバーが原因で、アライメントのためにかなりパディングされる可能性があります。 _unsigned long
_が4バイト幅の場合、sizeof (struct hack)
が8であるのに対し、offsetof(struct hack, foo)
はほぼ確実に5です。offsetof
メソッドは、配列の直前の構造体の前の部分の正確なサイズ。
つまり、それがコードをリファクタリングする方法になるでしょう。それは、古典的で移植性の高い構造ハックに準拠させることです。
ポインタを使用しないのはなぜですか?ポインタは余分なスペースを占有し、初期化する必要があるためです。
ポインターを使用しない理由は他にもあります。つまり、ポインターを意味のあるものにするためにはアドレス空間が必要です。構造体のハックは外部化可能です。つまり、そのようなレイアウトが、ファイル、パケット、または共有メモリの領域などの外部ストレージに準拠している場合がありますが、意味がないためポインタは必要ありません。
数年前、カーネルとユーザー空間間の共有メモリメッセージパッシングインターフェイスでstruct hackを使用しました。メッセージを生成するプロセスの元のアドレススペースに対してのみ意味があったので、そこにポインタは必要ありませんでした。ソフトウェアのカーネル部分は、別のアドレスで独自のマッピングを使用してメモリを表示するため、すべてオフセット計算に基づいていました。
IMOがサイズ計算を行うための最良の方法を指摘する価値があります。これは、上記のリンクされたRaymond Chenの記事で使用されています。
struct foo
{
size_t count;
int data[1];
}
size_t foo_size_from_count(size_t count)
{
return offsetof(foo, data[count]);
}
必要な割り当ての最後からの最初のエントリのオフセットも、必要な割り当てのサイズです。 IMOこれは、サイズ計算を行う非常にエレガントな方法です。可変サイズ配列の要素タイプは何でもかまいません。 offsetof(またはWindowsではFIELD_OFFSETまたはUFIELD_OFFSET)は常に同じ方法で書き込まれます。誤って失敗するsizeof()式はありません。