【C言語】複合リテラル【完全解説、生存期間、注意点、構造体/配列リテラル】

複合リテラル

複合リテラル(compound literal)は、配列型や構造体型の名前のないオブジェクトを表現するためのリテラルです。複合リテラルは「(型名){初期化子並び}」という括弧で囲まれた型名と波括弧で囲われた初期化子の並びを組み合わせる形で表現されます。

/* 配列の複合リテラル */
(int []){1, 2, 3};

/* 構造体の複合リテラル */
(struct Date){2018, 12, 24}; // struct Date { int year, month, day; };

/* ポインタ変数に格納可能 */
int *p = (int []){1, 2, 3};

/* 戻り値や実引数の値として利用可能 */
struct Date fn(struct Date date) {
   return (struct Date){2018, 12, 24}; // 戻り値として返す
}

fn((struct Date){2018, 12, 24}); // 実引数として渡す

複合リテラルは一般的なリテラル値のように、関数の実引数や戻り値として受け渡すことが可能です。一時変数を介する必要がないという特徴があります。

puts((char []){'a', 'b', '\0'});
printf("%s", (char []){'a', 'b', '\0'});

目次

配列の複合リテラル

配列型の値を複合リテラルで表現することができます。実際の値はポインタ型として受け取る必要があります。

// 配列リテラルとして表現可能
(int []){1, 2, 3};
// ポインタ変数に格納可能
int *p = (int []){1, 2, 3};

複合リテラルを配列型の初期化子として用いることはできません(ただしコンパイラ側の拡張機能として利用が可能な場合がある)

// error: initialization of an array of type 'int []' from a compound literal of type 'int [3]' is a GNU extension [-Werror,-Wgnu-compound-literal-initializer]
int a[] = (int []){1, 2, 3};

型名に配列のサイズを指定することは可能ですが、可変長配列としての宣言は行なえません。

int *a = (int [3]){1}; // `(int []){1, 0, 0}`と同等

/* 可変長配列(variable-length array)の利用は不可 */
int vla = 3;
int *a = (int [vla]){1}; // error: variable-sized object may not be initialized

/* 要素に定数以外の動的な値を指定することは可能 */
int v = 1;
int *a = (int [3]){v, v + 1};

構造体の複合リテラル

複合リテラルは構造体のリテラルを表現することも可能であり、宣言の際には初期化リストと同等の記法を用いることができます。

struct Date { int year, month, day; }; /* 構造体 */

// 構造体リテラルとして表現
(struct Date){2018, 12, 24};
// 指定初期化子の利用が可能
(struct Date){.year = 2018, .month = 12, .day = 24};

構造体の複合リテラルは構造体型の変数やポインタ変数に格納することが可能です。

struct Date val = (struct Date){2019, 12, 24};
struct Date *p = &(struct Date){2019, 12, 24};

p->year = 2049;
printf("%d, %d\n", val.year, p->year); // "2019, 2049"

複合リテラルの生存期間

複合リテラルの生存期間(記憶域期間)は複合リテラルが宣言されたスコープ内となります(自動記憶域期間)。

int *p;
{
   p = (int []){1, 2, 3};
   printf("%d", p[0]); // "1"
}
printf("%d", p[0]);    // "9" (不定な値)

自動記憶域期間を持った複合リテラルの値がポインタ型の戻り値として返された場合、受け取り側のポインタが指す値は不定なものとなります。

int *f() {
   return (int []){1, 2, 3}; // 危険(コンパイラ側の警告が発生しない場合もある)
}

int *p = f();
printf("%d", p[0]); // "9" (不定な値)

複合リテラルがファイルスコープ内で宣言された場合、複合リテラルの値の記憶域期間は静的記憶域期間となり、値はプログラムが終了するまで有効となります。

int* a = (int []){1, 2, 3}; // プログラムが終了するまで有効

int main() {
   printf("%d", a[0]); // "1"
}

複合リテラルの値として文字列リテラル用いた場合、丸括弧内の型名がchar []の際には、複合リテラルの値は自動記憶域期間を持ったchar型の配列で表現され、値の変更も可能となります。型名がconst char []const char *の場合は静的記憶域期間を持った読み取り専用の複合リテラルとなります。

char *automatic_storage = (char []){"abc"};
const char *static_storage = (const char []){"abc"};

読み取り専用の複合リテラルは、他の文字列リテラルと共通の記憶域を持つ場合があります。

"abc" == (const char *){"abc"};  // true  (処理系依存)
"abc" == (const char []){"abc"}; // false (処理系依存)

複合リテラルの記憶域

複合リテラルの値は常に一定の記憶域に確保されます。ループ内で複合リテラルを宣言した場合、ループの度に同じ領域が再利用されることになります。

int *a[2];
for (int i = 0; i < 2; i++) {
   int *p = (int []){i};  // `(int []){i}`の記憶域は毎回同じ
   a[i] = p;
}

a[0] == a[1];          // true (同一のポインタを保持)
printf("%d", a[0][0]); // "1"  (0ではない)
printf("%d", a[1][0]); // "1"

複合リテラルのサポート

複合リテラルはC99以降のC言語規格で利用が可能です。C++の標準規格ではサポートされていません。

/* C++では複合リテラルは利用できない */
// error: compound literals are a C99-specific feature [-Werror,-Wc99-extensions]
// warning: pointer is initialized by a temporary array, which will be destroyed at the end of the full-expression [-Waddress-of-array-temporary]
int *a = (int []){1, 2, 3};

// error: taking the address of a temporary object of type 'struct Date'
struct Date *p = &(struct Date){2018, 12, 24};

ただしC++コンパイラ側の対応によって利用可能になる場合もありますが、上記のwarning:ような警告が継続して発生する場合には、一時オブジェクトへのポインタという扱いになる恐れがあるため、コンパイラ側の未定義動作や不定な動作に注意する必要があります。

広告