DCL21-C. 複合リテラルの記憶域を理解する
C 標準 [ISO/IEC 9899:2011] のセクション 6.5.2.5 は、複合リテラルを次のように定義している。
括弧で囲まれた型名とそれに続く波括弧で囲まれた初期化子の並びで構成される後置式を複合リテラルと呼ぶ。. . . 複合リテラルの値は、初期化子並びで初期化された名前のないオブジェクトの値とする。
このオブジェクトの記憶域は、静的(複合リテラルがファイル全体に渡って使用される場合)または自動(複合リテラルがコードブロック内で使用される場合)のどちらかであり、記憶域期間はそれを囲む直近のブロックに結び付けられる。以下に例を示す。
void func(void) { int *ip = (int[4]){1,2,3,4}; /* ... */ }
この関数では、初期化の後、int
型ポインタ ip
には、スタックに割り当てられた int [4]
型の名前のないオブジェクトのアドレスが格納される。func
から戻ってきた後、このオブジェクトにアクセスしようとすると未定義の動作が引き起こされる。
複合リテラルがループ内で使用されている場合や、動的な初期化子をもっている場合でも、オブジェクトは複合リテラル 1 つにつき 1 つしか生成されないことに注意する。
このレコメンデーションは、「DCL30-C. 適切な記憶域期間を持つオブジェクトを宣言する」の具体的な事例である。
違反コード
次のコード例では、プログラマは、int_struct
へのポインタの ints
配列の要素には、個別の int_struct
オブジェクトのアドレスが、[0, MAX_INTS-1]
の範囲で、整数ごとに 1 つずつ代入されるものと誤って想定している。
#include <stdio.h> typedef struct int_struct { int x; } int_struct; #define MAX_INTS 10 int main(void){ size_t i; int_struct *ints[MAX_INTS]; for (i = 0; i < MAX_INTS; i++) { ints[i] = &(int_struct){i}; } for (i = 0; i < MAX_INTS; i++) { printf("%d\n", ints[i]->x); } }
しかし、生成されるのは 1 つの int_struct
オブジェクトだけである。最初のループの各反復において、このオブジェクトのメンバ x
は、ループカウンタ i
の現在の値と同じ値に設定される。ゆえに、最初のループが終了する直前のメンバ x
の値はMAX_INTS -1
となる。
複合リテラルの記憶域期間は、それを格納している for
ループに結び付けられるため、2 番目のループで ints
を参照すると未定義の動作 9(C 標準の附属書 J)が引き起こされる。
ループを複数回実行する間に、複合リテラルを格納していたメモリ領域への書き込みが行われなかったとしても、print ループは MAX_INTS-1
の値を MAX_INTS
行分表示する。これは、0
から MAX_INTS-1
までの整数が順番に出力されるという、直感的に想定される結果とは異なる。
適合コード
以下の適合コードは、ポインタの配列ではなく構造体の配列を使用している。このようにすると、各 int_struct
の(ポインタではなく)実際のコピーが格納される。
#include <stdio.h> typedef struct int_struct { int x; } int_struct; #define MAX_INTS 10 int main(void){ size_t i; int_struct ints[MAX_INTS]; for (i = 0; i < MAX_INTS; i++) { ints[i] = (int_struct){i}; } for (i = 0; i < MAX_INTS; i++) { printf("%d\n", ints[i].x); } }
リスク評価
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
DCL21-C |
低 |
低 |
中 |
P2 |
L3 |
参考資料
[ISO/IEC 9899:2011] | Section 6.5.2.5, "Compound Literals" |
翻訳元
これは以下のページを翻訳したものです。
DCL21-C. Understand the storage of compound literals (revision 32)