私は最近、C で柔軟な配列メンバーを使用することは、ソフトウェア エンジニアリングの実践として不適切であると読みました。しかし、その声明はいかなる議論にも裏付けられていませんでした。これは受け入れられた事実ですか?
(柔軟な配列メンバーは、C99 で導入された C の機能であり、最後の要素を未指定のサイズの配列として宣言できます。例: )
struct header {
size_t len;
unsigned char data[];
};
私は最近、C で柔軟な配列メンバーを使用することは、ソフトウェア エンジニアリングの実践として不適切であると読みました。しかし、その声明はいかなる議論にも裏付けられていませんでした。これは受け入れられた事実ですか?
(柔軟な配列メンバーは、C99 で導入された C の機能であり、最後の要素を未指定のサイズの配列として宣言できます。例: )
struct header {
size_t len;
unsigned char data[];
};
goto を使用することは、ソフトウェア エンジニアリングの手法として不適切であることは、受け入れられている「事実」です。それは真実ではありません。goto が便利な場合があります。特に、クリーンアップを処理するときや、アセンブラーから移植するときです。
柔軟な配列メンバーには、RiscOS のウィンドウ テンプレート フォーマットのようなレガシー データ フォーマットをマッピングするという 1 つの主な用途があるように思えます。約 15 年前であれば、これらは非常に便利だったでしょう。また、そのようなものを扱っている人がまだ有用であると考えている人がいると確信しています。
柔軟な配列メンバーを使用することが悪い習慣である場合は、C99 仕様の作成者にこのことを伝えに行くことをお勧めします。彼らは違う答えを持っているのではないかと思います。
この回答の下にあるコメントをよくお読みください
C の標準化が進むにつれ、[1] を使用する理由はもうありません。
そうしない理由は、この機能を使用するためだけにコードを C99 に関連付ける価値がないからです。
ポイントは、次のイディオムをいつでも使用できることです。
struct header {
size_t len;
unsigned char data[1];
};
それは完全に移植可能です。次に、配列内の n 要素にメモリを割り当てるときに 1 を考慮することができますdata
。
ptr = malloc(sizeof(struct header) + (n-1));
他の理由でコードをビルドするための要件として既に C99 を持っている場合、または特定のコンパイラをターゲットにしている場合、問題はないと思います。
いいえ、 C で柔軟な配列メンバーを使用することは悪い習慣ではありません。
この言語機能は、ISO C99、6.7.2.1 (16) で最初に標準化されました。次のリビジョン ISO C11 では、セクション 6.7.2.1 (18) で指定されています。
次のように使用できます。
struct Header {
size_t d;
long v[];
};
typedef struct Header Header;
size_t n = 123; // can dynamically change during program execution
// ...
Header *h = malloc(sizeof(Header) + sizeof(long[n]));
h->n = n;
または、次のように割り当てることもできます。
Header *h = malloc(sizeof *h + n * sizeof h->v[0]);
最終的なパディング バイトが含まれていることに注意してくださいsizeof(Header)
。したがって、次の割り当ては正しくなく、バッファ オーバーフローが発生する可能性があります。
Header *h = malloc(sizeof(size_t) + sizeof(long[n])); // invalid!
つまり、1 つの構造体オブジェクトに対して 2 つの割り当てを行う代わりに、1 つだけで済みます。つまり、メモリ アロケータのブックキーピング オーバーヘッドが占める労力とメモリが少なくて済みます。さらに、1 つの追加ポインター用のストレージを保存します。したがって、そのような構造体インスタンスを多数割り当てなければならない場合、プログラムの実行時間とメモリ使用量がかなり改善されます (一定の割合で)。
それとは対照的に、未定義の動作を生成する柔軟な配列メンバー ( long v[0];
or などlong v[1];
) に標準化されていない構造を使用することは、明らかに悪い習慣です。したがって、未定義の動作と同様に、これは避ける必要があります。
20 年以上前の 1999 年に ISO C99 がリリースされて以来、ISO C89 との互換性を求めることは説得力のない議論です。
あなたが意味したのは...
struct header
{
size_t len;
unsigned char data[];
};
C では、これが一般的なイディオムです。多くのコンパイラも受け入れていると思います:
unsigned char data[0];
はい、それは危険ですが、繰り返しになりますが、通常の C 配列よりも実際には危険ではありません。つまり、非常に危険です ;-) . 未知のサイズの配列が本当に必要な状況でのみ、注意して使用してください。次のようなものを使用して、メモリを正しく malloc および解放していることを確認してください。
foo = malloc(sizeof(header) + N * sizeof(data[0]));
foo->len = N;
別の方法は、データを要素へのポインタにすることです。その後、必要に応じてデータを正しいサイズに realloc() できます。
struct header
{
size_t len;
unsigned char *data;
};
もちろん、C++ について質問している場合、これらのいずれかは悪い習慣になります。次に、通常、代わりに STL ベクトルを使用します。
私はこのようなものを見てきました: C インターフェイスと実装から。
struct header {
size_t len;
unsigned char *data;
};
struct header *p;
p = malloc(sizeof(*p) + len + 1 );
p->data = (unsigned char*) (p + 1 ); // memory after p is mine!
注: data は最後のメンバーである必要はありません。
補足として、C89 との互換性のために、そのような構造体は次のように割り当てる必要があります。
struct header *my_header
= malloc(offsetof(struct header, data) + n * sizeof my_header->data);
またはマクロで:
#define FLEXIBLE_SIZE SIZE_MAX /* or whatever maximum length for an array */
#define SIZEOF_FLEXIBLE(type, member, length) \
( offsetof(type, member) + (length) * sizeof ((type *)0)->member[0] )
struct header {
size_t len;
unsigned char data[FLEXIBLE_SIZE];
};
...
size_t n = 123;
struct header *my_header = malloc(SIZEOF_FLEXIBLE(struct header, data, n));
FLEXIBLE_SIZE を SIZE_MAX に設定すると、ほぼ確実に失敗します。
struct header *my_header = malloc(sizeof *my_header);