閉じる

【C言語】変数を初期化しないとどうなる? 未初期化の落とし穴

C言語では、変数を「宣言するだけ」で使えてしまうため、うっかり初期化を忘れてもコンパイルが通ることがあります。

しかし、そのまま実行すると結果が毎回変わったり、原因不明の不具合につながったりします。

未初期化は典型的なバグの温床です。

本記事では、未初期化変数の正体と危険性、そして確実に回避するための初期化の基本とコツを、初心者の方にも分かりやすく解説します。

C言語の未初期化変数とは?

未初期化変数とは、宣言はしたものの何の値も代入していない変数のことです。

C言語では、記憶域期間(スコープではなくメモリの性質)によって「初期化されない場合の状態」が異なります。

初期化の意味

初期化とは、変数を定義したときに最初の値を与えることを指します。

たとえば、int x = 0;のように書きます。

Cでは初期化されていないローカル変数の値は「不定」であり、読み出すと未定義動作になります。

これは「何が起きてもおかしくない」というC規格上の取り扱いです。

ローカル変数は初期値なし

関数の中で宣言した通常の変数(自動記憶域)は、初期化しない限り不定値を持ちます。

次のコードはあくまで「危険例」で、結果は環境やビルド設定によって変わります。

C言語
#include <stdio.h>

int main(void) {
    int x;                // ローカル変数。初期化していない(不定値)
    printf("x = %d\n", x); // xの読み出しは未定義動作(実行ごとに出力が変わる可能性)
    return 0;
}

実行例(環境により異なります)

実行結果
x = 32764

同じプログラムでも、別の実行例では次のようになることがあります。

実行結果
x = 0

このように、未初期化の値に意味はありません

たまたま0に見えても「0に初期化された」わけではないことに注意してください。

グローバルとstaticは0になる

一方で、グローバル変数やstatic指定子を持つ変数(静的記憶域)は、明示的に初期化しなかった場合必ず0に初期化されます。

C言語
#include <stdio.h>

int g;            // グローバル変数。明示しなくても0で初期化される

void show(void) {
    static int s; // 関数内staticも0で初期化される
    printf("g = %d, s = %d\n", g, s);
}

int main(void) {
    show();
    return 0;
}
実行結果
g = 0, s = 0

この違いを整理しておくと混乱しにくくなります。

記憶域期間(代表例)初期化しない場合の初期値読み出しの可否と注意点
自動(関数内の通常の変数)int x;不定読み出しは未定義動作(何が起きても不思議でない)
静的(グローバル/static)static int y;0読み出し可能(0が入る)
動的(ヒープ: malloc領域)int *p = malloc(...);不定読み出しは未定義動作。callocは0埋め
注意

動的確保領域は構成外ですが、実務では重要です。

メモリの動的確保の場合、mallocは未初期化、callocは0初期化であることを覚えておくと安全です。

変数を初期化しないとどうなる?

未初期化はとにかく危険です。

実行するたびに結果が変わる、開発環境を変えると挙動が変わる、最適化の有無で変わるなど、デバッグを難しくします。

条件分岐が誤判定

ifの条件に未初期化変数を使うのは致命的です。

分岐の真偽が実行ごとに変わることがあります。

C言語
#include <stdio.h>

int main(void) {
    int flag; // 初期化していない
    if (flag) {
        printf("ON\n");
    } else {
        printf("OFF\n");
    }
    return 0;
}

実行例1

実行結果
OFF

実行例2(同じバイナリでも起こり得ます)

実行結果
ON

コンパイラによっては0で初期化してくれることもありますが、絶対ではありません。「たまたま正しく動いた」ように見えても、それは偶然です

計算結果が不安定

未初期化の値を使った計算は、当然ながら結果が不安定です。

C言語
#include <stdio.h>

int main(void) {
    int a;            // 未初期化
    int b = 10;
    int sum = a + b;  // aの値が不定なのでsumも不定
    printf("a = %d, b = %d, sum = %d\n", a, b, sum);
    return 0;
}
実行結果
a = -1059357568, b = 10, sum = -1059357558

別の実行例

実行結果
a = 0, b = 10, sum = 10

このような不安定さはテストでは見逃されやすく、本番環境で発火する典型的なバグ要因です。

予測不能な動作(未定義動作)

未初期化を使うと、想定外の範囲アクセスなどの二次被害を誘発します。

たとえば、未初期化のインデックスで配列にアクセスすると、境界外アクセスを引き起こすかもしれません。

C言語
#include <stdio.h>

int main(void) {
    int idx;               // 未初期化
    int arr[4] = {0, 1, 2, 3};
    // idxの値しだいで配列範囲外アクセス(未定義動作)になる可能性
    printf("arr[idx] = %d\n", arr[idx]);
    return 0;
}

このコードは「クラッシュするか」「一見動くか」は実行時の偶然次第です。

未定義動作は環境・最適化・実行タイミングによって結果が変わり続けます

デバッグが困難になる

未初期化バグは、以下の理由で特に厄介です。

  • デバッガでステップ実行するとたまたま値が変わり、再現しないことがあるため。
  • 最適化(-O2など)でコードが変形され、症状が消えたり悪化したりするため。
  • ログを入れると挙動が変わり、根本原因に辿り着けないため。

「再現率が低い不具合」「環境依存で壊れる」場合、未初期化を真っ先に疑うのが良い習慣です。

変数の初期化の基本

原則はシンプルです。

宣言したら初期化する

そして使う直前に最適な初期値を与える

この2点を守るだけで、多くの事故を防げます。

宣言と同時に初期化(int x = 0;)

最も確実で読みやすい初期化方法です。

C言語
#include <stdio.h>

int main(void) {
    int x = 0; // 宣言と同時に初期化
    printf("x = %d\n", x);
    return 0;
}
実行結果
x = 0

使う直前に初期化

スコープを狭く保ち、値の意味が最もはっきりする場所で初期化します。

これにより、誤用の余地が減ります。

C言語
#include <stdio.h>

int main(void) {
    int n = 3;
    int sum = 0;

    for (int i = 0; i < n; ++i) {
        int value = i * 2;  // 使う直前で初期化
        sum += value;
    }

    printf("sum = %d\n", sum); // 0 + 2 + 4 = 6
    return 0;
}
実行結果
sum = 6

数値型の例(int,double)

整数や実数では、0や意味のある初期値を与えましょう。

C言語
#include <stdio.h>

int main(void) {
    int    count = 0;        // カウンタは0から
    double ratio = 1.0;      // 比率は1.0など意味のある初期値
    double result = count + ratio; // 0 + 1.0 = 1.0

    printf("count = %d, ratio = %.2f, result = %.2f\n", count, ratio, result);
    return 0;
}
実行結果
count = 0, ratio = 1.00, result = 1.00

文字型の例(char)

文字1個ならシングルクォート、文字列ならNUL終端に注意します。

空文字を表したい場合は'\0'または0を使います。

C言語
#include <stdio.h>

int main(void) {
    char ch  = 'A';   // 文字の初期化
    char nul = '
#include <stdio.h>
int main(void) {
char ch  = 'A';   // 文字の初期化
char nul = '\0';  // NUL文字(数値0)
char str[] = "Hello"; // 'H','e','l','l','o','\0' の6バイト
printf("ch = %c, nul = %d, str = %s\n", ch, nul, str);
return 0;
}
'; // NUL文字(数値0) char str[] = "Hello"; // 'H','e','l','l','o','
#include <stdio.h>
int main(void) {
char ch  = 'A';   // 文字の初期化
char nul = '\0';  // NUL文字(数値0)
char str[] = "Hello"; // 'H','e','l','l','o','\0' の6バイト
printf("ch = %c, nul = %d, str = %s\n", ch, nul, str);
return 0;
}
' の6バイト printf("ch = %c, nul = %d, str = %s\n", ch, nul, str); return 0; }
実行結果
ch = A, nul = 0, str = Hello

配列の初期化の基本

配列はブレース{}を使うと安全に初期化できます。

部分初期化では、指定しなかった要素は0になります。

C言語
#include <stdio.h>

int main(void) {
    int a[5] = {0};           // 全要素が0で初期化
    int b[5] = {1, 2, 3};     // 残り2要素は0で初期化(= {1,2,3,0,0})
    char s1[6] = "Hello";     // 'H','e','l','l','o','
#include <stdio.h>
int main(void) {
int a[5] = {0};           // 全要素が0で初期化
int b[5] = {1, 2, 3};     // 残り2要素は0で初期化(= {1,2,3,0,0})
char s1[6] = "Hello";     // 'H','e','l','l','o','\0'
char s2[6] = {0};         // 6バイトすべて0(空文字列)
printf("a: ");
for (int i = 0; i < 5; ++i) printf("%d ", a[i]);
printf("\n");
printf("b: ");
for (int i = 0; i < 5; ++i) printf("%d ", b[i]);
printf("\n");
printf("s1 = %s, s2 length = %zu\n", s1, strlen(s2));
return 0;
}
' char s2[6] = {0}; // 6バイトすべて0(空文字列) printf("a: "); for (int i = 0; i < 5; ++i) printf("%d ", a[i]); printf("\n"); printf("b: "); for (int i = 0; i < 5; ++i) printf("%d ", b[i]); printf("\n"); printf("s1 = %s, s2 length = %zu\n", s1, strlen(s2)); return 0; }
実行結果
a: 0 0 0 0 0 
b: 1 2 3 0 0 
s1 = Hello, s2 length = 0
補足

構造体や配列をすべて0で初期化したい場合、Cでは= {0}が簡潔で安全です。

memset(&obj, 0, sizeof obj)も0初期化には使えますが、0以外の値での初期化には適しません。

未初期化を防ぐコツ

ミスを「起こさない」工夫と「起きても気付ける」仕組みの両立が重要です。

コンパイラ警告を有効化(-Wall)

GCCやClangでは、最低限-Wall -Wextraを有効にし、可能なら-Wuninitializedも使います。

WindowsのMSVCなら/W4以上がおすすめです。

Shell
# GCC/Clangの例(C17でビルド)
gcc -std=c17 -Wall -Wextra -O0 sample.c -o sample
# さらに未初期化検出の強化
gcc -std=c17 -Wall -Wextra -Wuninitialized -O0 sample.c -o sample

警告例

実行結果
sample.c: In function 'main':
sample.c:5:22: warning: 'x' is used uninitialized [-Wuninitialized]
    5 |     printf("%d\n", x);
      |                      ^

実行時検出の支援として、Clangなら-fsanitize=memory、GCC/Clangなら-fsanitize=address,undefinedが有用です(学習段階では特に効果的)。

必ず初期値を入れる習慣

  • ローカル変数は宣言と同時に意味のある初期値を入れます。決められないなら0NULL、ブールならfalse(#include <stdbool.h>)を使います。
  • 条件分岐に使うフラグは必ず初期化し、初期状態を明確にします。
  • 構造体や配列は= {0}で一括初期化すると安全です。

使わない変数を作らない

未使用変数は初期化漏れの温床です。

必要になる直前まで宣言しない、使わない変数は削除する、ビルドで-Werror=unused-variableを使って未使用をエラー化する、などの方針が効果的です。

「宣言は最小限、スコープは最小限」を合言葉にしましょう。

まとめ

未初期化変数は、C言語のバグの中でも発見が難しく、被害が大きい代表格です。

ローカル変数は初期値なし(不定)、グローバルやstaticは0になるというルールを正しく理解し、宣言と同時の初期化使う直前の適切な初期化を徹底しましょう。

配列や構造体は= {0}で安全に初期化でき、コンパイラ警告(-Wall -Wextra -Wuninitialized)やサニタイザで未初期化の芽を早期に摘むことができます。

「初期化しないまま使わない」これだけで、原因不明の不具合の多くを未然に防げます

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!