最近、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];
};
それは完全に移植可能です。次に、配列data
のn個の要素にメモリを割り当てるときに1を考慮することができます。
ptr = malloc(sizeof(struct header) + (n-1));
他の理由でコードをビルドするための要件として既にC99を使用している場合、または特定のコンパイラーをターゲットにしている場合、問題はありません。
あなたは...
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で フレキシブル配列メンバー を使用することは悪い習慣ではありません。
この言語機能は、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つの構造体オブジェクトに対して2つの割り当てを行うのではなく、1つだけが必要です。さらに、1つの追加ポインタ用にストレージを保存します。したがって、このような構造体インスタンスを多数割り当てる必要がある場合は、プログラムの実行時間とメモリ使用量を(一定の要因で)大幅に改善できます。
それとは対照的に、未定義の動作をもたらす柔軟な配列メンバーに標準化されていない構造を使用することは(たとえば_long v[0];
_または_long v[1];
_のように)明らかに悪い習慣です。したがって、未定義の動作として、これは避ける必要があります。
ISO C99はほぼ20年前の1999年にリリースされて以来、ISO C89の互換性を追求することは弱い議論です。
私はこのようなものを見ました: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!
注:データは最後のメンバーである必要はありません。
構造体が時々使用される方法に関連するいくつかの欠点があります、そして、あなたが含意を通して考えないならば、それは危険でありえます。
たとえば、関数を開始する場合:
void test(void) {
struct header;
char *p = &header.data[0];
...
}
その後、結果は未定義です(データ用のストレージが割り当てられていないため)。これは通常知っていることですが、Cプログラマーが構造体に値のセマンティクスを使用できることに慣れている場合があり、これはさまざまな方法で分類されます。
たとえば、私が定義する場合:
struct header2 {
int len;
char data[MAXLEN]; /* MAXLEN some appropriately large number */
}
次に、単に割り当てによって2つのインスタンスをコピーできます。
struct header2 inst1 = inst2;
または、ポインターとして定義されている場合:
struct header2 *inst1 = *inst2;
ただし、変数配列data
はコピーされないため、これは機能しません。必要なのは、構造体のサイズを動的にmallocし、memcpy
または同等の配列をコピーすることです。
同様に、関数呼び出しの引数は値によってコピーされるため、構造体を受け入れる関数の作成は機能しません。したがって、取得されるのはおそらく配列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);