配列の初期化は、C言語の学習初期に必ず通る大切なテーマです。
正しい初期化は、バグの少ない安全なプログラムの第一歩です。
本記事では、整数や浮動小数点、文字配列まで、配列の初期化の基本と注意点を、実行例つきで丁寧に解説します。
初心者の方でも読みながら実験できるよう、具体的なサンプルコードを多数用意しました。
C言語の配列の初期化
宣言と初期化の書き方
配列は型名 配列名[要素数]
で宣言し、= { 初期化子, ... }
の形で初期化します。
初期化は宣言と同時に1回だけ行えます。
次の表は、よく使う初期化パターンと意味の概要です。
書き方 | 例 | 意味と挙動 |
---|---|---|
すべて指定 | int a[3] = {1, 2, 3}; | 3要素すべてを指定どおりに初期化します。 |
一部指定(部分初期化) | int a[5] = {1, 2}; | 指定しなかった残り要素は0で初期化されます。 |
サイズ省略 | int a[] = {10, 20, 30, 40}; | 要素数を省略すると、初期化子の個数から自動で決定されます(この例では4)。 |
全ゼロ | int a[5] = {0}; | 最初の要素を0指定すると、残りもすべて0になります。全消去に便利です。 |
文字列リテラル | char s[] = "abc"; | 末尾に終端文字'\0' が追加され、実際の要素数は4になります。 |
残りを0埋めする性質は非常に有用です。
例えば設定テーブルなどで、指定していない部分を自動で0にできます。
以下は基本的な宣言と初期化、そして内容の確認です。
#include <stdio.h>
int main(void) {
// 完全初期化
int a1[3] = {1, 2, 3};
// 部分初期化(残りは0)
int a2[5] = {1, 2};
// サイズ省略(初期化子個数が要素数になる)
int a3[] = {10, 20, 30, 40};
// 全ゼロ初期化
int zeros[5] = {0};
// 内容を表示する(シンプルなループ)
printf("a1: ");
for (size_t i = 0; i < 3; ++i) printf("%d ", a1[i]);
printf("\n");
printf("a2: ");
for (size_t i = 0; i < 5; ++i) printf("%d ", a2[i]);
printf("\n");
printf("a3(len=%zu): ", sizeof(a3) / sizeof(a3[0]));
for (size_t i = 0; i < sizeof(a3) / sizeof(a3[0]); ++i) printf("%d ", a3[i]);
printf("\n");
printf("zeros: ");
for (size_t i = 0; i < 5; ++i) printf("%d ", zeros[i]);
printf("\n");
return 0;
}
a1: 1 2 3
a2: 1 2 0 0 0
a3(len=4): 10 20 30 40
zeros: 0 0 0 0 0
初期化子リストのルール
初期化子は{ 値1, 値2, ... }
のようにカンマ区切りで書きます。最後にコンマを置いてもOK(多くのCコンパイラで許容、標準Cでも可能)です。
int a[3] = {1, 2, 3,};
各初期化子は配列の要素型へ変換されます。例えばdouble
配列に整数リテラルを入れると、小数点付きに自動変換されます。
逆にint
配列へ小数を入れると小数部分は切り捨てられます。型に合った定数を使うのが安全です。
上級テクニックとして指定初期化子(designated initializer)があります。
int a[5] = {[2] = 42}; // a[2]だけ42、他は0
初心者のうちは通常の並び順で十分です。
配列サイズの省略
初期化子を与える場合、要素数を省略するとコンパイラが数えてくれます。
特に文字列リテラルは終端文字'\0'
も含めて数えられる点に注意してください。
#include <stdio.h>
int main(void) {
int x[] = {5, 6, 7}; // 要素数3
char s[] = "abc"; // 'a','b','c','#include <stdio.h>
int main(void) {
int x[] = {5, 6, 7}; // 要素数3
char s[] = "abc"; // 'a','b','c','\0' の4要素
char t[] = {'a','b','c'}; // 3要素(終端なし)
printf("x: 要素数 = %zu\n", sizeof(x)/sizeof(x[0]));
printf("s: 要素数 = %zu\n", sizeof(s)/sizeof(s[0]));
printf("t: 要素数 = %zu\n", sizeof(t)/sizeof(t[0]));
return 0;
}
' の4要素
char t[] = {'a','b','c'}; // 3要素(終端なし)
printf("x: 要素数 = %zu\n", sizeof(x)/sizeof(x[0]));
printf("s: 要素数 = %zu\n", sizeof(s)/sizeof(s[0]));
printf("t: 要素数 = %zu\n", sizeof(t)/sizeof(t[0]));
return 0;
}
x: 要素数 = 3
s: 要素数 = 4
t: 要素数 = 3
文字列を扱う%s
のような関数は終端文字が必要です。
終端の無いt
を%s
で出力してはいけません。
ゼロ初期化
全要素を0にしたいときは= {0}
が簡単です。
また、静的記憶域(ファイルスコープ変数、またはstatic
が付いたローカル配列)は、明示的な初期化がなくても0で初期化されます。
#include <stdio.h>
// ファイルスコープ(静的記憶域)は暗黙に0初期化
int g[4];
int main(void) {
static int s[3]; // staticローカルも暗黙に0初期化
int a[3] = {0}; // 明示的に全ゼロ
printf("g: ");
for (size_t i = 0; i < 4; ++i) printf("%d ", g[i]);
printf("\n");
printf("s: ");
for (size_t i = 0; i < 3; ++i) printf("%d ", s[i]);
printf("\n");
printf("a: ");
for (size_t i = 0; i < 3; ++i) printf("%d ", a[i]);
printf("\n");
return 0;
}
g: 0 0 0 0
s: 0 0 0
a: 0 0 0
ブロック内で宣言した非staticなローカル配列は、明示しない限りゼロ初期化されません。
これについては後述の注意点で解説します。
整数配列と浮動小数点配列の初期化
完全初期化
整数でも浮動小数点でも、全要素を列挙すれば完全初期化です。
#include <stdio.h>
int main(void) {
int ai[4] = {1, -2, 3, 4};
double ad[3] = {1.0, 2.5, -0.0}; // -0.0は符号付きゼロとして扱われることがあります
printf("ai: ");
for (size_t i = 0; i < 4; ++i) printf("%d ", ai[i]);
printf("\n");
printf("ad: ");
for (size_t i = 0; i < 3; ++i) printf("%.2f ", ad[i]);
printf("\n");
return 0;
}
ai: 1 -2 3 4
ad: 1.00 2.50 -0.00
部分初期化
初期化子が足りない場合、残りは0で初期化されます。
浮動小数点も0.0
になります。
#include <stdio.h>
int main(void) {
int ai[5] = {42}; // 先頭のみ42、他は0
double ad[4] = {1.5, 2.5}; // 残りは0.0
printf("ai: ");
for (size_t i = 0; i < 5; ++i) printf("%d ", ai[i]);
printf("\n");
printf("ad: ");
for (size_t i = 0; i < 4; ++i) printf("%.1f ", ad[i]);
printf("\n");
return 0;
}
ai: 42 0 0 0 0
ad: 1.5 2.5 0.0 0.0
定数式サイズと初期化の組み合わせ
配列の要素数に定数式を使うのが基本です。
マクロや列挙定数がよく使われます。
#include <stdio.h>
#define N 3 // マクロは定数式として使える
enum { M = 4 }; // 列挙定数も定数式
int main(void) {
int a[N] = {1, 2, 3};
double b[M] = {0}; // 全ゼロ
printf("a(len=%zu), b(len=%zu)\n",
sizeof(a)/sizeof(a[0]), sizeof(b)/sizeof(b[0]));
return 0;
}
a(len=3), b(len=4)
Cではconst int n = 3;
のn
は「定数式」ではありません。
ブロック内でint a[n];
のように書くと可変長配列(VLA)になり、VLAは初期化子リストで初期化できません。
// NG例 (多くの処理系でエラー):
// const int n = 3;
// int a[n] = {1, 2, 3}; // VLAは初期化できない
文字配列の初期化
文字列リテラルで初期化
文字列リテラルで初期化すると、末尾に自動で'\0'
が入るため、%s
で安全に表示できます。
#include <stdio.h>
#include <string.h>
int main(void) {
char s1[] = "hello"; // 実際の要素数は6('h','e','l','l','o','#include <stdio.h>
#include <string.h>
int main(void) {
char s1[] = "hello"; // 実際の要素数は6('h','e','l','l','o','\0')
printf("s1 = %s\n", s1);
printf("sizeof(s1) = %zu, strlen(s1) = %zu\n",
sizeof(s1), strlen(s1));
return 0;
}
')
printf("s1 = %s\n", s1);
printf("sizeof(s1) = %zu, strlen(s1) = %zu\n",
sizeof(s1), strlen(s1));
return 0;
}
s1 = hello
sizeof(s1) = 6, strlen(s1) = 5
文字配列と終端文字の注意
終端文字'\0'
が入ることを見越して要素数を確保してください。
次は混同しやすい例です。
// OK: 4文字 + '// OK: 4文字 + '\0' = 5
char ok1[5] = "hell";
// NG: "hello" は6文字(終端含む)なので要素数5では足りない
// char ng1[5] = "hello"; // コンパイルエラー
// OKだが「文字列」ではない: 終端が無い
char not_str[4] = {'h','e','l','l'};
' = 5
char ok1[5] = "hell";
// NG: "hello" は6文字(終端含む)なので要素数5では足りない
// char ng1[5] = "hello"; // コンパイルエラー
// OKだが「文字列」ではない: 終端が無い
char not_str[4] = {'h','e','l','l'};
終端の無い配列を%s
で出力するのは危険です。
必要なら自分で長さを管理し、ループで出力します。
#include <stdio.h>
int main(void) {
char not_str[4] = {'h','e','l','l'}; // 終端なし
// 安全な出力(要素数を使って回す)
for (size_t i = 0; i < 4; ++i) {
putchar(not_str[i]);
}
putchar('\n');
return 0;
}
hell
1文字ずつの初期化
1文字ずつ並べる方法は、必要なら最後に'\0'
を自分で入れるのがポイントです。
#include <stdio.h>
int main(void) {
char word[4] = {'A', 'B', 'C', '#include <stdio.h>
int main(void) {
char word[4] = {'A', 'B', 'C', '\0'}; // ちゃんと終端を入れる
printf("%s\n", word); // ABC
return 0;
}
'}; // ちゃんと終端を入れる
printf("%s\n", word); // ABC
return 0;
}
ABC
初期化でつまずきやすい注意点
配列全体への代入は不可
Cでは配列同士を=
で丸ごと代入できません。
次はエラーになります。
// NG: 配列への代入はできない
// int a[3], b[3] = {1,2,3};
// a = b; // コンパイルエラー
配列の内容をコピーしたい場合はループを使います。
#include <stdio.h>
int main(void) {
int src[3] = {1, 2, 3};
int dst[3]; // 未初期化、これからコピーする
// 要素ごとのコピー
for (size_t i = 0; i < 3; ++i) {
dst[i] = src[i];
}
// 結果確認
printf("dst: ");
for (size_t i = 0; i < 3; ++i) printf("%d ", dst[i]);
printf("\n");
return 0;
}
dst: 1 2 3
バイト配列のコピーにはmemcpy
も使えますが、まずはループで確実に書けるようにするのがおすすめです。
要素数より多い初期化子はエラー
指定した要素数を超える初期化子はコンパイルエラーです。
// NG:
// int a[2] = {1, 2, 3}; // 初期化子が多すぎる
// 修正案:
// int a[3] = {1, 2, 3};
// あるいは
// int a[2] = {1, 2};
ローカル配列の未初期化は不定値で危険
ブロック内(関数内)の通常のローカル配列は、明示的に初期化しないと中身は不定です。
読み出すと予測不能な値になります。
#include <stdio.h>
int main(void) {
int a[3]; // 未初期化(不定値)
printf("a: ");
for (size_t i = 0; i < 3; ++i) {
// 環境によっては警告が出ます(-Wuninitializedなど)
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
a: 32764 0 4199040 (例。実行のたびに異なる可能性があります)
未初期化のまま使わないこと。
必要に応じて={0}
やループで初期化しましょう。
全要素を同じ値にするにはループが必要
全要素を任意の値で揃えるにはループで代入します。
ゼロにするだけなら={0}
が最短です。
#include <stdio.h>
#include <string.h>
int main(void) {
int a[6];
// 1) ゼロで初期化(宣言時)
int z[6] = {0};
// 2) 任意の値(ここでは7)で埋める: ループが正攻法
for (size_t i = 0; i < 6; ++i) {
a[i] = 7;
}
// 3) memsetは「0で埋める」には使えるが、整数の任意値には不向き
int m[6];
memset(m, 0, sizeof m); // 0ならOK(全ビット0が整数0に対応)
// 出力
printf("z: ");
for (size_t i = 0; i < 6; ++i) printf("%d ", z[i]);
printf("\n");
printf("a(=7): ");
for (size_t i = 0; i < 6; ++i) printf("%d ", a[i]);
printf("\n");
printf("m(memset 0): ");
for (size_t i = 0; i < 6; ++i) printf("%d ", m[i]);
printf("\n");
return 0;
}
z: 0 0 0 0 0 0
a(=7): 7 7 7 7 7 7
m(memset 0): 0 0 0 0 0 0
memset(a, 7, sizeof a);
のように非ゼロで埋めると「各バイトを0x07で埋める」だけなので、整数の7とは無関係のビットパターンになり、意図した結果にはなりません。
整数や浮動小数点を任意値で揃える場合は必ずループで代入しましょう。
まとめ
配列の初期化はシンプルに見えて、終端文字や要素数、定数式の扱いなど細かなルールを正しく理解することが大切です。
特に次のポイントを押さえておけば、初期化で迷うことはほとんどありません。
- 部分初期化では残りが0で埋まるため、必要なところだけ書けばよい。全ゼロは
={0}
でOK。 - サイズ省略は便利。ただし文字列リテラルでは
'\0'
も含まれる点に注意。 - ローカル配列の未初期化は厳禁。使う前に初期化する。
- 配列に代入はできないので、コピーはループで行う。
- 定数式サイズを使う(マクロや列挙定数)。VLAは初期化子リストで初期化できない。
これらを守れば、配列の初期化は怖くありません。
サンプルを手元で動かしながら、「いつ0になるか」「サイズがどう決まるか」「終端が入るか」を体感して、自信をつけていきましょう。