DCL38-C. フレキシブル配列メンバには正しい構文を使用する
フレキシブル配列メンバ(flexible array member)とは、2 つ以上の名前付きメンバをもつ構造体の最後のメンバが不完全配列型、つまり、構造体の中で配列のサイズが明確に指定されていない、特殊な型になっている配列を指す。struct hack と呼ばれるこの手法は広く使われており、さまざまなコンパイラが対応している。それゆえ、フレキシブル配列メンバの宣言にはさまざまな構文が使用されてきた。C 標準に適合した実装では、C 標準によって有効であると保証されている構文を使用すること。
フレキシブル配列メンバは、C 標準のセクション 6.7.2.1 パラグラフ 18 において次のように定義されている[ISO/IEC 9899:2011]。
特別な場合として、2 つ以上の名前付きメンバをもつ構造体の最後のメンバは、不完全配列型をもってもよい。これをフレキシブル配列メンバ(flexible array member)と呼ぶ。二つの例外を除いて、フレキシブル配列メンバは無視される。一つ目は、構造体の大きさは、フレキシブル配列メンバを未規定の長さの配列に置き換えて、それ以外は元のままとした構造体の最後のメンバのオフセットに等しいとする。二つ目は、
.
(または->
)演算子がフレキシブル配列メンバをもつ構造体(またはその構造体へのポインタ)を左オペランドとしてもち、かつ右オペランドがそのメンバであるとき、その動作は、構造体の大きさが現在アクセスされているオブジェクトの大きさより大きくならないという条件のもとで、そのメンバが同じ要素型をもつ最大の大きさの配列で置き換えられた場合と同じとする。このとき、たとえ置き換えられた配列のオフセットがそのメンバのオフセットと異なるとしても、そのメンバのオフセットは変更しない。置き換えられた配列が要素をもたないとき、それはただ一つの要素をもつのと同じ規則で動作する。しかし、その要素にアクセスした場合、又はその要素を一つ越えたポインタを生成した場合、その動作は未定義とする。
フレキシブル配列をもつ構造体を使ったコードが未定義の動作とならないためには、以下の制約を満たしていなければならない。
- 不完全配列型は必ず構造体の最後の要素となっていること。
- フレキシブル配列メンバを含む構造体の配列を使わないこと。
- フレキシブル配列メンバを含む構造体を、ほかの構造体の途中にあるメンバとして使用しないこと。
- フレキシブル配列メンバを含む構造体には、必ず他に名前付きのメンバが 1 つ以上含まれていること。
違反コード
C 標準にフレキシブル配列メンバが採用される前は、最後のメンバとして要素数が 1 つの配列をもつ構造体を使用して同様の機能を達成していた。以下のコード例は、この場合に struct flexArrayStruct
をどのように宣言するかを示している。
以下のコードでは、構造体の最後のメンバとして要素数 1 つの配列を置くことによってフレキシブル配列メンバに相当するものを実現している。この構造体を実際にメモリ上に割り付ける際には、配列メンバに収める実際の要素数を考慮して計算された値が malloc()
の引数として渡される。
#include <stdlib.h>
struct flexArrayStruct {
int num;
int data[1];
};
void func(size_t array_size) {
/* 構造体のためのメモリ領域をアロケートする */
struct flexArrayStruct *structP
= (struct flexArrayStruct *)
malloc(sizeof(struct flexArrayStruct)
+ sizeof(int) * (array_size - 1));
if (structP == NULL) {
/* malloc エラーの処理 */
}
structP->num = array_size;
/*
* data[array_size] として割り当てられているかのように
* data[] をアクセスする.
*/
for (size_t i = 0; i < array_size; ++i) {
structP->data[i] = 1;
}
}
このコード例では、配列の2番め以降の要素にアクセスした場合の動作は未定義である。(C 標準のセクション 6.5.6 を参照。) したがって、配列の 2 番めの要素にアクセスしたときに期待される値を返さないようなコードが生成される可能性がある。
C 標準のフレキシブル配列メンバをサポートしていないコンパイラを使用する場合は、このアプローチをとらざるをえないだろう。
適合コード
以下の適合コードでは、フレキシブル配列メンバを使用し、実行時にサイズを設定する構造体を実現している。
#include <stdlib.h>
struct flexArrayStruct{
int num;
int data[];
};
void func(size_t array_size) {
/* 構造体のためのメモリ領域を割り当て */
struct flexArrayStruct *structP
= (struct flexArrayStruct *)
malloc(sizeof(struct flexArrayStruct)
+ sizeof(int) * array_size);
if (structP == NULL) {
/* malloc エラーの処理 */
}
structP->num = array_size;
/*
* data[array_size] として割り当てられているかのように
* data[] をアクセス.
*/
for (size_t i = 0; i < array_size; ++i) {
structP->data[i] = 1;
}
}
この適合コードでは、C 標準に適合した方法で、メンバ data[]
を data[array_size]
と宣言したかのように構造体を扱うことができる。
リスク評価
C 標準で規定された正しい構文でフレキシブル配列メンバを宣言しなければ、未定義の動作を引き起こす可能性がある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
DCL38-C |
低 |
低 |
低 |
P3 |
L3 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Astrée |
19.04
|
array_out_of_bounds | Supported: Astrée reports out-of-bounds array access. |
Axivion Bauhaus Suite |
6.9.0 |
CertC-DCL38 | Detects if the final member of struct which is declared as an array of small bound, is used as a flexible array member. |
Compass/ROSE |
|
|
このルールの違反を検出できる。特に、 |
LDRA tool suite |
9.7.1
|
648 S | 実装済み |
Parasoft C/C++test |
10.4.1 |
CERT_C-DCL38-a |
The final member of a structure should not be an array of size '0' or '1' |
PRQA QA-C |
9.5
|
1037 1039 |
|
関連するガイドライン
このガイドラインは「MEM33-C. フレキシブル配列メンバを含む構造を動的に割り当ててコピーする」を補足するものである。
参考資料
[ISO/IEC 9899:2011] | 6.5.6, "Additive Operators" 6.7.2.1, "Structure and Union Specifiers" |
[McCluskey 2001] | "Flexible Array Members and Designators in C9X" |
翻訳元
これは以下のページを翻訳したものです。
DCL38-C. Use the correct syntax when declaring a flexible array member (revision 64)