C言語で構造体を正しく初期化できると、意図した値が常に入った安全なデータを扱えます。
とくにゼロ初期化や指定初期化子を使い分けると、ミスを減らし読みやすさも上がります。
本記事では構造体の初期化を、基本から指定初期化子まで段階的に整理し、実用パターンと注意点まで丁寧に解説します。
部分初期化とゼロ初期化のやり方
一部のメンバーだけ初期化
どのような振る舞いになるか
構造体は波かっこで並べる通常の初期化子で、先頭から順に値を与えます。
未指定のメンバーは自動的に0で初期化されます。
例えば、struct Profile { char name[16]; int age; double height; };
に対して{"Alice"}
と書くと、name
だけが”Alice”、age
は0、height
は0.0になります。
なお、char name[16] = "Alice"
のように文字配列へ文字列リテラルを入れると、終端の'\0'
を含めて残りは0で埋まります。
どんな時に便利か
- 先頭から順に指定できるなら、
{"名前", 年齢}
のように必要なところだけ書いて端数は0に任せられます。 - 入れ子になっている場合でも、外側の未指定メンバーや内側の未指定メンバーは0になります。
{0}で全メンバーを0初期化
役割と効果
{0}は「とにかく全部0にしたい」時のもっとも簡潔で安全な書き方です。
最初の要素に0を与えると、残りは規格の規則で0に展開されます。
ポインタメンバーもNULL
になり、浮動小数点は0.0
になり、配列は全要素が0になります。
いつ使うか
- 初期状態を明示したい時
- 不要なゴミ値を避けたい時
- メンバーが多い大きな構造体の一括初期化
入れ子の構造体を初期化
書き方の基本
入れ子の構造体は、{{...}}
のように内側の構造体用にもう一段波かっこを使います。
例えばPerson { Profile profile; Date birthday; }
があるときは{ ..., {年, 月, 日} }
と書きます。
内側も省略したメンバーは0になります。
よくある誤り
- 内側の波かっこを省いて値を並べると、意図しないメンバーに入ることがあります。読みやすさのためにも
{{...}}
を使うのがおすすめです。
以下の表は、未指定部分がどう初期化されるかの要点をまとめたものです。
対象 | 例 | 省略時の値 |
---|---|---|
構造体のメンバー | struct S { int a; double b; } s = { 10 }; | a=10 、b=0.0 |
配列の要素 | int a[4] = { 1, 2 }; | a[2]=0 、a[3]=0 |
入れ子のメンバー | Outer o = { { 1 }, 5 }; | 内側の未指定も0 |
ポインタメンバー | Node n = {0}; | next=NULL |
文字配列 | char s[8] = "Hi"; | 'H','i','\0',0,0,0,0,0 |
章末サンプルコードと出力
以下は「一部だけ」「{0}」「入れ子」の各初期化をまとめて確認するプログラムです。
#include <stdio.h>
// 日付を表す入れ子用の構造体
typedef struct {
int year;
int month;
int day;
} Date;
// プロフィール情報の構造体
typedef struct {
char name[16]; // 短い名前用の固定長バッファ
int age;
double height;
Date birthday;
} Profile;
int main(void) {
// 一部のメンバーだけ初期化(先頭から順に)
Profile a = { "Alice" }; // age=0, height=0.0, birthdayも0
Profile b = { "Bob", 20 }; // height=0.0, birthdayも0
// {0}で完全ゼロ初期化
Profile zeroed = { 0 }; // 全メンバーが0
// 入れ子の構造体を初期化(内側はもう1組の波かっこ)
Profile c = { "Carol", 30, 165.5, { 2000, 1, 2 } };
// 入れ子の一部だけ初期化(残りは0)
Profile d = { "Dave", 40, 170.0, { 2020 } }; // month=0, day=0
// 結果を表示
printf("a: name=%s, age=%d, height=%.1f, bday=%04d-%02d-%02d\n",
a.name, a.age, a.height, a.birthday.year, a.birthday.month, a.birthday.day);
printf("b: name=%s, age=%d, height=%.1f, bday=%04d-%02d-%02d\n",
b.name, b.age, b.height, b.birthday.year, b.birthday.month, b.birthday.day);
printf("zeroed: name=\"%s\", age=%d, height=%.1f, bday=%04d-%02d-%02d\n",
zeroed.name, zeroed.age, zeroed.height,
zeroed.birthday.year, zeroed.birthday.month, zeroed.birthday.day);
printf("c: name=%s, age=%d, height=%.1f, bday=%04d-%02d-%02d\n",
c.name, c.age, c.height, c.birthday.year, c.birthday.month, c.birthday.day);
printf("d: name=%s, age=%d, height=%.1f, bday=%04d-%02d-%02d\n",
d.name, d.age, d.height, d.birthday.year, d.birthday.month, d.birthday.day);
return 0;
}
a: name=Alice, age=0, height=0.0, bday=0000-00-00
b: name=Bob, age=20, height=0.0, bday=0000-00-00
zeroed: name="", age=0, height=0.0, bday=0000-00-00
c: name=Carol, age=30, height=165.5, bday=2000-01-02
d: name=Dave, age=40, height=170.0, bday=2020-00-00
指定初期化子の使い方
指定初期化子とは
定義と利点
指定初期化子とは、.member = 値
の形でメンバー名を明示して初期化するC99以降の機能です。
順番を気にせず、スキップもしやすいので、メンバーが多い構造体や入れ子が深いときに読みやすさと安全性が上がります。
古いC規格(C89など)や一部の古いコンパイラでは使えない場合があります。
必要ならコンパイルオプションでC99以上を有効にしてください。
順不同でメンバーを初期化
使い方の例
struct Config { int id; double ratio; char tag[8]; int flags[4]; };
に対して、.ratio
→.id
→.tag
のように順不同に書けます。
読み手は「何に何を入れたか」をすぐ理解できます。
省略したメンバーは0になる
暗黙のゼロ初期化
指定していないメンバーは、通常の初期化と同じく0に初期化されます。
たとえば.flags
を省略すると全要素0になります。
配列メンバーの指定初期化
配列要素の個別指定
配列メンバーには[index] = 値
の形を使って個別に指定できます。
入れ子でも使えるので、.flags = { [2] = 7 }
や.tag = { [0] = 'A', [3] = 'Z' }
のように書けます。
未指定の要素は0です。
章末サンプルコードと出力
指定初期化子の順不同、未指定は0、配列の個別指定を一度に確認します。
#include <stdio.h>
typedef struct {
int id;
double ratio;
char tag[8];
int flags[4];
} Config;
int main(void) {
// 順不同で指定(未指定のflagsは0)
Config c1 = {
.ratio = 0.75,
.id = 42,
.tag = "CFG" // 末尾は'#include <stdio.h>
typedef struct {
int id;
double ratio;
char tag[8];
int flags[4];
} Config;
int main(void) {
// 順不同で指定(未指定のflagsは0)
Config c1 = {
.ratio = 0.75,
.id = 42,
.tag = "CFG" // 末尾は'\0'で埋まる
};
// 配列メンバーの指定初期化(未指定は0)
Config c2 = {
.id = 1,
.flags = { [2] = 7 }, // flags[2]だけ7、他は0
.tag = { [0]='A', [3]='Z' }, // tag[0]='A', tag[1]=0, tag[2]=0, tag[3]='Z', 他は0
.ratio = 1.25
};
printf("c1: id=%d, ratio=%.2f, tag=%s, flags=[%d %d %d %d]\n",
c1.id, c1.ratio, c1.tag, c1.flags[0], c1.flags[1], c1.flags[2], c1.flags[3]);
printf("c2: id=%d, ratio=%.2f, tag bytes=[%d %d %d %d %d %d %d %d], flags=[%d %d %d %d]\n",
c2.id, c2.ratio,
(unsigned char)c2.tag[0], (unsigned char)c2.tag[1], (unsigned char)c2.tag[2], (unsigned char)c2.tag[3],
(unsigned char)c2.tag[4], (unsigned char)c2.tag[5], (unsigned char)c2.tag[6], (unsigned char)c2.tag[7],
c2.flags[0], c2.flags[1], c2.flags[2], c2.flags[3]);
return 0;
}
'で埋まる
};
// 配列メンバーの指定初期化(未指定は0)
Config c2 = {
.id = 1,
.flags = { [2] = 7 }, // flags[2]だけ7、他は0
.tag = { [0]='A', [3]='Z' }, // tag[0]='A', tag[1]=0, tag[2]=0, tag[3]='Z', 他は0
.ratio = 1.25
};
printf("c1: id=%d, ratio=%.2f, tag=%s, flags=[%d %d %d %d]\n",
c1.id, c1.ratio, c1.tag, c1.flags[0], c1.flags[1], c1.flags[2], c1.flags[3]);
printf("c2: id=%d, ratio=%.2f, tag bytes=[%d %d %d %d %d %d %d %d], flags=[%d %d %d %d]\n",
c2.id, c2.ratio,
(unsigned char)c2.tag[0], (unsigned char)c2.tag[1], (unsigned char)c2.tag[2], (unsigned char)c2.tag[3],
(unsigned char)c2.tag[4], (unsigned char)c2.tag[5], (unsigned char)c2.tag[6], (unsigned char)c2.tag[7],
c2.flags[0], c2.flags[1], c2.flags[2], c2.flags[3]);
return 0;
}
c1: id=42, ratio=0.75, tag=CFG, flags=[0 0 0 0]
c2: id=1, ratio=1.25, tag bytes=[65 0 0 90 0 0 0 0], flags=[0 0 7 0]
実用パターンと注意点
構造体の配列を初期化
まとめて初期化する
構造体の配列も、個々の要素を{ }
で並べれば簡潔に初期化できます。
[index] = {...}
で特定の要素だけを明示的に初期化することもできます。
未指定の要素や未指定のメンバーは0になります。
例と出力
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
typedef struct {
int a;
double b;
char c[4];
} State;
// グローバル変数(静的記憶期間)は何も書かなくても0初期化される
State g_state; // a=0, b=0.0, cは全て0
// 静的ローカル変数も同様に自動で0初期化される
void show_static_state(void) {
static State s; // a=0, b=0.0, cは全て0
printf("static s: a=%d, b=%.1f, c bytes=[%d %d %d %d]\n",
s.a, s.b, (unsigned char)s.c[0], (unsigned char)s.c[1],
(unsigned char)s.c[2], (unsigned char)s.c[3]);
}
int main(void) {
// 構造体の配列を初期化。未指定の要素(ここではindex=2)は0。
Point ps[4] = {
{1, 2}, // index 0
{3}, // index 1: x=3, y=0
[3] = {9, 9} // index 3だけ明示
};
for (int i = 0; i < 4; ++i) {
printf("ps[%d] = { x=%d, y=%d }\n", i, ps[i].x, ps[i].y);
}
// グローバル構造体の自動0初期化を確認
printf("g_state: a=%d, b=%.1f, c bytes=[%d %d %d %d]\n",
g_state.a, g_state.b, (unsigned char)g_state.c[0], (unsigned char)g_state.c[1],
(unsigned char)g_state.c[2], (unsigned char)g_state.c[3]);
// 静的ローカル構造体の自動0初期化を確認
show_static_state();
return 0;
}
ps[0] = { x=1, y=2 }
ps[1] = { x=3, y=0 }
ps[2] = { x=0, y=0 }
ps[3] = { x=9, y=9 }
g_state: a=0, b=0.0, c bytes=[0 0 0 0]
static s: a=0, b=0.0, c bytes=[0 0 0 0]
読みやすい初期化のコツ
実務で効く書き方
「何に何を入れているか」を一目で分かる形にするのが重要です。
指定初期化子やコメント整形で可読性を上げられます。
次のようなスタイルが有効です。
// 指定初期化子で意味を明確化し、行末コメントで用途を補足
typedef struct {
int width;
int height;
int x;
int y;
} Rect;
Rect r = {
.width = 128, // 画像の幅
.height = 64, // 画像の高さ
.x = 0, // 左上X
.y = 0 // 左上Y
};
文中では、次のような方針が読みやすさと安全性を両立します。
- 初期状態は
{0}
で明確にゼロ化する - 複雑な構造体は指定初期化子で<メンバー名と値>を揃える
- 入れ子には波かっこ
{{...}}
を省かず使う - 文字配列は長さに余裕を持ち、終端
'\0'
を意識する
よくあるエラーと対策
代表例と修正
- 入れ子に波かっこを忘れる
誤り:
typedef struct { int a; int b[3]; } S;
S s = { .a = 1, 2, 3, 4 }; // b[0]=2, b[1]=3, b[2]=4 にはなるが意図が不明瞭
修正:
S s = { .a = 1, .b = { 2, 3, 4 } }; // 内側に波かっこで明確化
- 文字列が配列サイズを超える
誤り:
char name[4] = "ABCDE"; // 終端'char name[4] = "ABCDE"; // 終端'\0'を含め5文字で溢れる
'を含め5文字で溢れる
修正:
char name[6] = "ABCDE"; // 又は "ABCD" を使う
- 指定初期化子が使えない環境
C89準拠や古いコンパイラでは指定初期化子がエラーになります。
対策:-std=c99
や-std=c11
でビルドし、コンパイラのバージョンを確認します。 - グローバル初期化で定数式ではない値を使う
誤り:
int f(void);
struct S { int x; } g = { f() }; // 静的記憶期間の初期化に関数呼び出しは不可
実行時に設定するか、定数式で初期化します。
- memsetでの一律ゼロ埋めの濫用
初期化時は{0}
で十分です。実行時にリセットしたい場合でも浮動小数点やポインタ表現を意識しなければならないため、まずは{0}
の使える場面を優先しましょう。C規格上、ゼロ初期化はポインタに正しいNULL
値を与えます。
グローバルな構造体は自動で0初期化
規格のルール
静的記憶期間のオブジェクト(グローバル変数、ファイルスコープのstatic、関数内static)は、明示しなくても0初期化されます。
これは実用上とても便利で、初期状態の安全性が高まります。
上のサンプルではg_state
とstatic State s
が該当し、いずれも0で始まっています。
使い分けの目安
- かならず0で始めたいグローバル設定オブジェクトは、あえて何も書かず自動0初期化に任せる
- 「特定の値で始めたい」場合は指定初期化子で明示する
まとめ
構造体の初期化は、ゼロ初期化と指定初期化子を使い分けるのが鍵です。
部分初期化では未指定が0になること、{0}
で全メンバーを一発で0にできること、入れ子は波かっこで包むことを押さえておくと、安全で読みやすいコードになります。
さらに指定初期化子を用いれば順不同でメンバーを明示でき、配列メンバーも[index]
で個別指定できます。
最後に、グローバルやstaticな構造体は自動で0初期化されるという規則も強力です。
プロジェクトでは、初期状態は{0}
、意味のある値は指定初期化子、配列や入れ子は波かっこ、といった指針で統一すると、ミスの少ない堅牢なコードになります。