C言語の基本|変数を安全に使うための初期化

変数は値を入れてから使う。初期化の習慣が、予測できる安全なプログラムをつくる。

これまでの学習では、変数がデータを保存するための入れ物であり、使用する前にデータ型と名前を指定して宣言することを学びました。

たとえば、次のように書くとint型のscoreという変数を宣言できます。

int score;

しかし、変数は宣言しただけで安心して使えるとは限りません。

main関数などの中で宣言したローカル変数には、自動的に0が入るわけではないからです。値を設定していないローカル変数は、どのような値として読み取られるか分からない状態になります。

この状態の値を不定値と呼びます。

不定値の変数をprintfで表示したり、計算に使ったりすると、プログラムの動作を予測できなくなります。たまたま0や小さな整数が表示されても、その値を正しいものとして利用してはいけません。

そこで大切になるのが、初期化と代入です。

  • 初期化は、変数の宣言と同時に最初の値を設定する
  • 代入は、宣言した変数へ後から値を設定する
  • 変数の値を読み取る前に、必ず初期化または代入を済ませる
  • 最初の値が決まっている場合は、宣言時の初期化を優先する

このパートでは、初期化と代入の違いを整理しながら、変数を安全に使うための基本的な習慣を身につけていきます。

宣言しただけのローカル変数は安全に使えない

次のコードでは、scoreを宣言しています。

int score;

この時点で、scoreを保存するための領域はメモリ上に用意されます。

ただし、scoreへ値は設定されていません。関数の中で宣言したこのようなローカル変数は、宣言しただけでは0に初期化されません。

そのため、次のように値を読み取って表示してはいけません。

int score;

printf("%d\n", score);

scoreへ値を設定する前に、その内容をprintfで読み取っています。このような処理の動作はC言語では保証されません。

不定値とは何か

不定値とは、変数へ正しい値を設定していないため、内容を予測できない状態を表します。

メモリは、プログラムのさまざまな処理で繰り返し使われます。新しく変数を宣言したとき、その領域に以前の処理で使われた情報が残っているように見えることがあります。

しかし、その内容を現在の変数の値として使うことはできません。

変数の状態値を安全に使用できるか
宣言しただけ使用できない
宣言時に初期化した使用できる
宣言後に代入した使用できる
scanfで正常に入力できた使用できる
scanfによる入力が失敗した使用できるとは限らない

宣言しただけの変数について、値がランダムに入っていると説明されることがあります。しかし、毎回きれいな乱数が入るという意味ではありません。

大切なのは、どの値になるかを予測できず、値を読み取る処理そのものが正しくないという点です。

不定値は0とは限らない

次の宣言を見てみましょう。

int count;

countはint型ですが、宣言しただけでは0になりません。

たまたま実行時に0のような値が見える環境があったとしても、次回の実行でも0になる保証はありません。コンパイラ、最適化設定、実行するタイミングなどが変わると、動作が変わる可能性があります。

次のように最初から0を設定すれば、countを安全に使えます。

int count = 0;

不定値を使うと起こりやすい問題

不定値を読み取るコードには、次のような問題があります。

  • 計算結果を予測できない
  • 実行するたびに動作が変わるように見える
  • 条件分岐が意図しない方向へ進む
  • 繰り返し回数がおかしくなる
  • 配列の位置や処理件数が誤る
  • 開発環境を変えると問題が現れる
  • コンパイラの最適化によって動作が変わる
  • バグが毎回再現せず、原因を調べにくくなる

不定値を使ったプログラムが一度正常に動いたように見えても、安全なコードになったわけではありません。

変数は、値を読み取る前に必ず初期化または代入しましょう。

図1:宣言しただけの変数と初期化した変数

この図から分かること

変数を宣言するとメモリ上に領域は用意されますが、関数内で宣言したローカル変数へ自動的に0が入るわけではありません。

宣言しただけのscoreは、まだ安全に読み取れない状態です。一方、int score = 0;と書けば、宣言と同時に0で初期化されるため、その後の表示や計算に使えます。

変数を宣言したことと、変数へ有効な値を設定したことは、別の状態として考えましょう。

不定値の変数を表示するプログラムは実行しない

不定値の危険性を確認するために、次のようなコードを書きたくなるかもしれません。

int score1;
int score2;

printf("%d\n", score1);
printf("%d\n", score2);

しかし、このコードは未初期化のローカル変数を読み取っています。正しい実行結果は定義されていないため、表示された値を教材上の正解として扱うことはできません。

コンパイラから、未初期化の変数が使われている可能性を示す警告が出る場合もあります。

不定値を安全に学ぶには、危険な処理を実行するのではなく、未初期化の値を読み取らないプログラムにします。

未初期化の変数へ値を入れてから表示するプログラム

次のプログラムでは、score1とscore2を最初に宣言します。宣言直後には表示せず、値を代入してからprintfで表示します。

宣言した変数へ値を代入してから表示するプログラム

ファイル名:4_3_1.c

// 宣言した変数へ値を代入してから表示するプログラム
#include <stdio.h>

int main(void)
{
    int score1;   // この時点では値を読み取らない
    int score2;   // この時点では値を読み取らない

    score1 = 72;  // 表示する前に値を代入
    score2 = 88;  // 表示する前に値を代入

    printf("変数へ値を入れてから表示します。\n");
    printf("score1の値は%dです。\n", score1);
    printf("score2の値は%dです。\n", score2);

    return 0;
}
実行結果の例
変数へ値を入れてから表示します。
score1の値は72です。
score2の値は88です。

このプログラムでは、score1とscore2を宣言した直後には読み取っていません。

int score1;
int score2;

その後、両方の変数へ値を代入しています。

score1 = 72;
score2 = 88;

printfが実行される時点では、どちらの変数にも有効な値が設定されています。そのため、表示結果を予測できます。

安全な順番を確認しよう

変数を宣言してから値を使うまでの安全な順番は、次のとおりです。

  1. 変数を宣言する
  2. 初期化または代入によって値を設定する
  3. printfや計算で値を読み取る

4_3_1.cでは、次の順番になっています。

int score1;
score1 = 72;
printf("%d\n", score1);

一方、次の順番では、値を設定する前に読み取っているため正しくありません。

int score1;
printf("%d\n", score1);
score1 = 72;

代入の行が書かれていても、printfより後にある場合は間に合いません。プログラムは上から順番に実行されるため、値を読み取る処理より前に設定する必要があります。

代入とは宣言後に値を設定すること

代入とは、すでに宣言されている変数へ値を入れる操作です。

基本的な書き方は、次のとおりです。

変数名 = 値;

たとえば、double型の変数へ円周率に近い値を代入する場合は、次のように書きます。

double pi;

pi = 3.14159;

1行目は変数の宣言です。

double pi;

2行目は値の代入です。

pi = 3.14159;

代入が終わった後は、piの値を表示や計算に使えます。

printf("%.5f\n", pi);

=は右側の値を左側へ入れる

C言語で使う=は、代入演算子です。

x = 10;

このコードは、右辺にある10を、左辺にあるxへ代入することを表しています。

部分役割
x値を保存する変数
=右辺の値を左辺へ代入する
10xへ保存する値

数学では=を等しいという意味で使いますが、C言語の代入では右側の値を左側へ入れると考えると分かりやすくなります。

変数の値は代入によって変更できる

通常の変数には、後から別の値を代入できます。

int count = 5;

count = 8;
count = 12;

最初にcountを5で初期化しています。

その後、8を代入し、さらに12を代入しています。最後に保存されている値は12です。

処理countの値
int count = 5;5
count = 8;8
count = 12;12

新しい値を代入すると、それまで保存されていた値は上書きされます。

初期化とは宣言と同時に最初の値を設定すること

初期化とは、変数を宣言すると同時に、最初の値を設定することです。

基本的な書き方は、次のとおりです。

データ型 変数名 = 初期値;

たとえば、double型のpiを宣言し、同時に3.14159を設定する場合は次のように書きます。

double pi = 3.14159;

この1行には、次の2つの内容が含まれています。

  • double型のpiを宣言する
  • piの最初の値を3.14159にする

宣言した直後から値が決まっているため、piを安全に利用できます。

データ型に合わせて初期値を書く

データ型に合わせた初期化の例は、次のとおりです。

int count = 0;
double total = 0.0;
char grade = 'A';
データ型変数初期値
intcount0
doubletotal0.0
chargradeA

char型へ1文字を直接設定するときは、C言語の文字定数として単一引用符を使います。

char grade = 'A';

これは命令や引数を強調するための引用ではなく、C言語で1文字を表すために必要な文法です。

初期化と代入の違い

初期化と代入は、どちらも変数へ値を設定します。ただし、値を設定するタイミングが違います。

項目初期化代入
タイミング変数の宣言と同時変数を宣言した後
主な目的最初の値を決める後から値を設定または変更する
int x = 10;x = 10;
データ型を書くか書く書かない

初期化では、新しい変数を宣言するため、データ型を書きます。

int x = 10;

代入では、すでに宣言されている変数を使うため、データ型を書きません。

x = 20;

次の2行では、1行目が初期化、2行目が代入です。

int x = 10;
x = 20;

図2:初期化と代入の違い

この図から分かること

初期化では、変数を用意することと、最初の値を設定することを同時に行います。

代入では、すでに用意されている変数へ値を設定します。変数に以前の値が入っている場合は、新しい値によって上書きされます。

初期化と代入は似ていますが、宣言と同時に行うか、宣言した後に行うかという違いがあります。

初期化と代入を使い分けるプログラム

次のプログラムでは、読書冊数を初期化し、メモを取った回数を宣言後に代入します。

初期化と代入で読書記録を設定するプログラム

ファイル名:4_3_2.c

// 初期化と代入で読書記録を設定するプログラム
#include <stdio.h>

int main(void)
{
    int book_count = 4;   // 宣言と同時に初期化
    int memo_count;       // 変数だけを宣言

    memo_count = 16;      // 宣言後に代入

    printf("読書記録を表示します。\n");
    printf("読んだ本は%d冊です。\n", book_count);
    printf("作成したメモは%d件です。\n", memo_count);

    return 0;
}
実行結果の例
読書記録を表示します。
読んだ本は4冊です。
作成したメモは16件です。

book_countは初期化している

book_countは、宣言と同時に4を設定しています。

int book_count = 4;

データ型、変数名、最初の値が1行に書かれているため、初期化です。

この宣言が終わった時点で、book_countには4が入っています。

memo_countは宣言後に代入している

memo_countは、最初に変数だけを宣言しています。

int memo_count;

この時点では、値を読み取ってはいけません。

その後、16を代入しています。

memo_count = 16;

printfが実行される前に代入が完了しているため、memo_countを安全に表示できます。

初期化を優先するメリット

最初の値が分かっている場合は、宣言と同時に初期化する方法がおすすめです。

初期化には、次のメリットがあります。

  • 未初期化の状態を短くできる
  • 不定値を誤って読み取る危険を減らせる
  • 変数の最初の値をすぐに確認できる
  • 宣言と値の設定が離れない
  • コードを読みやすくできる
  • バグを減らしやすい
  • デバッグしやすくなる

たとえば、合計値を記録する変数は0から始めることが多いため、次のように初期化できます。

int total = 0;

件数を数える変数も、最初に0を設定できます。

int count = 0;

小数の合計値には、0.0を設定できます。

double total_time = 0.0;

最初の文字が決まっている場合は、char型も初期化できます。

char grade = 'A';

代入が必要になる場面

すべての変数を、最初から意味のある値で初期化できるとは限りません。

プログラムの実行中に値が決まる場合は、宣言後の代入やscanfによる入力を使います。

代入が使われる代表的な場面は、次のとおりです。

  • キーボードから入力された値を保存する
  • 計算結果を保存する
  • 条件に応じて値を変更する
  • 繰り返し処理の中で値を更新する
  • 関数の処理結果を保存する
  • ファイルから読み取った値を保存する

簡単な例を見てみましょう。

int score;

score = 80;

scoreの値が宣言後に決まるため、代入しています。

計算結果を保存する場合は、次のように書けます。

int total;

total = 30 + 20;

右辺の30 + 20が先に計算され、その結果である50がtotalへ代入されます。

scanfで値を受け取る場合

scanfは、入力された値を変数へ保存します。

int score;

scanf("%d", &score);

入力が正常に読み取られた場合は、scoreへ値が設定されます。

ただし、数字を入力する場所で文字を入力するなど、scanfによる読み取りが失敗すると、scoreへ値が設定されない可能性があります。

安全性を高めるには、scanfの戻り値を確認します。

int score = 0;

printf("点数を入力してください。\n");

if (scanf("%d", &score) == 1)
{
    printf("入力された点数は%d点です。\n", score);
}
else
{
    printf("整数を正しく入力できませんでした。\n");
}

scanfで整数を1つ正常に読み取ると、戻り値は1になります。

この詳しい条件分岐は後の学習内容ですが、scanfを使ったから必ず変数へ値が入るとは限らないことを覚えておきましょう。

初期段階では、入力先の変数を0などで初期化しておく方法も、未初期化状態を避ける助けになります。

=と==を区別しよう

C言語では、=と==は異なる役割を持ちます。

記号役割使用例
=値を代入するscore = 80;
==2つの値が等しいか比較するscore == 80

代入は、変数へ値を入れる処理です。

score = 80;

比較は、左右の値が等しいかを調べる処理です。

score == 80

条件分岐を学ぶと、==を使う機会が増えます。今の段階では、=は代入、==は比較と区別しておきましょう。

一部の変数だけ値を入れ忘れない

複数の変数を宣言したときは、一部の変数だけ代入を忘れやすくなります。

次のコードでは、first_scoreには値が設定されていますが、second_scoreには値が設定されていません。

int first_score;
int second_score;

first_score = 70;

printf("%d %d\n", first_score, second_score);

second_scoreは未初期化のまま読み取られるため、正しくありません。

両方の変数へ値を設定してから使います。

int first_score;
int second_score;

first_score = 70;
second_score = 85;

printf("%d %d\n", first_score, second_score);

最初の値が決まっているなら、宣言時に初期化すると入れ忘れを防ぎやすくなります。

int first_score = 70;
int second_score = 85;

同じ型の変数をまとめて初期化するときの注意

同じ型の変数は、カンマで区切って宣言できます。

int first_score, second_score;

初期化も同じ行に書けます。

int first_score = 70, second_score = 85;

ただし、次の書き方ではfirst_scoreだけが初期化されています。

int first_score = 0, second_score;

= 0がsecond_scoreにも適用されるわけではありません。

変数状態
first_score0で初期化されている
second_score初期化されていない

両方を0で初期化する場合は、それぞれに= 0を書きます。

int first_score = 0, second_score = 0;

初期化の状態を見落としやすい場合は、1行ずつ宣言すると分かりやすくなります。

int first_score = 0;
int second_score = 0;

初期化と代入をまとめて確認するプログラム

次のプログラムでは、最初から決まっている開始時刻を初期化し、後から決まる終了時刻と作業時間を代入します。

初期化と代入で作業記録を設定するプログラム

ファイル名:4_3_3.c

// 初期化と代入で作業記録を設定するプログラム
#include <stdio.h>

int main(void)
{
    int start_hour = 9;       // 開始時刻は宣言時に初期化
    int finish_hour;          // 終了時刻は後から設定
    double work_hours;        // 作業時間は後から設定

    finish_hour = 17;         // 宣言後に代入
    work_hours = 7.5;         // 宣言後に代入

    printf("作業開始は%d時です。\n", start_hour);
    printf("作業終了は%d時です。\n", finish_hour);
    printf("実際の作業時間は%.1f時間です。\n", work_hours);

    return 0;
}
実行結果の例
作業開始は9時です。
作業終了は17時です。
実際の作業時間は7.5時間です。

start_hourは宣言時に初期化する

作業の開始時刻は最初から9時と決まっているため、宣言と同時に初期化しています。

int start_hour = 9;

宣言後に別の行で9を代入することもできますが、最初の値が決まっているなら初期化したほうが簡潔です。

finish_hourは宣言後に代入する

finish_hourは宣言後に17を代入しています。

int finish_hour;

finish_hour = 17;

宣言した時点では値を読み取らず、代入後にprintfで表示しています。

work_hoursへ小数を代入する

work_hoursはdouble型です。

double work_hours;

後から7.5を代入しています。

work_hours = 7.5;

printfでは%.1fを使い、小数点以下を1桁表示します。

printf("実際の作業時間は%.1f時間です。\n", work_hours);

変数を安全に使うための確認ポイント

変数を宣言したら、その値を読み取る前に、値が設定されているか確認しましょう。

確認するとよいポイントは、次のとおりです。

  • 宣言時に初期化されているか
  • 宣言後に代入されているか
  • printfより前に値が設定されているか
  • 計算で使う前に値が設定されているか
  • 複数の変数すべてに値が入っているか
  • scanfが正常に入力を受け取ったか
  • 条件によって代入されない経路が残っていないか
  • まとめて宣言した変数の一部だけを初期化していないか

図3:変数を安全に使うための確認手順

この図から分かること

最初の値が決まっている場合は、宣言と同時に初期化します。

宣言時には値を決められない場合は、printfや計算で読み取る前に代入します。どちらの方法でも、値が設定されてから利用することが重要です。

scanfなど、値の設定に失敗する可能性がある処理では、本当に値が入ったか確認することも必要になります。

初期化と代入の実践問題

次のコメントを参考にして、①から⑥へコードを入れてください。

#include <stdio.h>

int main(void)
{
    // int型のvisit_daysとshop_countを宣言し、
    // visit_daysを6、shop_countを14で初期化する
    ➀
    ➁

    printf("訪問日数は%d日、訪れた店舗は%d店です。\n",
           visit_days, shop_count);

    // double型のstay_hoursとtarget_hoursをまとめて宣言する
    ➂

    // stay_hoursへ3.25、target_hoursへ5.50を代入する
    ➃
    ➄

    printf("滞在時間は%.2f時間、目標時間は%.2f時間です。\n",
           stay_hours, target_hours);

    // char型のarea_rankを文字コード66で初期化する
    ➅

    printf("地域ランクは%cです。\n", area_rank);

    return 0;
}

実行結果は、次のようになります。

訪問日数は6日、訪れた店舗は14店です。
滞在時間は3.25時間、目標時間は5.50時間です。
地域ランクはBです。

考えるポイントは次のとおりです。

  • visit_daysとshop_countは宣言と同時に初期化する
  • stay_hoursとtarget_hoursは同じdouble型として宣言する
  • stay_hoursとtarget_hoursは宣言後に代入する
  • area_rankはchar型として初期化する
  • すべての変数へ値を設定してからprintfで表示する

実践問題の解答プログラム

初期化と代入を組み合わせて地域情報を表示するプログラム

ファイル名:4_3_4.c

// 初期化と代入を組み合わせて地域情報を表示するプログラム
#include <stdio.h>

int main(void)
{
    // int型のvisit_daysとshop_countを宣言し、
    // visit_daysを6、shop_countを14で初期化する
    int visit_days = 6;
    int shop_count = 14;

    printf("訪問日数は%d日、訪れた店舗は%d店です。\n",
           visit_days, shop_count);

    // double型のstay_hoursとtarget_hoursをまとめて宣言する
    double stay_hours, target_hours;

    // stay_hoursへ3.25、target_hoursへ5.50を代入する
    stay_hours = 3.25;
    target_hours = 5.50;

    printf("滞在時間は%.2f時間、目標時間は%.2f時間です。\n",
           stay_hours, target_hours);

    // char型のarea_rankを文字コード66で初期化する
    char area_rank = 66;

    printf("地域ランクは%cです。\n", area_rank);

    return 0;
}
実行結果の例

ASCIIと互換性のある一般的なWindows 11とGCCの環境では、次のように表示されます。

訪問日数は6日、訪れた店舗は14店です。
滞在時間は3.25時間、目標時間は5.50時間です。
地域ランクはBです。

①と②ではint型の変数を初期化する

visit_daysとshop_countは、最初から値が決まっています。

int visit_days = 6;
int shop_count = 14;

宣言と同時に値を設定しているため、どちらも初期化です。

2つの変数をまとめて書くこともできます。

int visit_days = 6, shop_count = 14;

ただし、1行ずつ書いたほうが、それぞれの変数と初期値の対応を確認しやすくなります。

③ではdouble型の変数を宣言する

stay_hoursとtarget_hoursは、同じdouble型です。

double stay_hours, target_hours;

この時点では、まだ値を読み取ってはいけません。

直後に両方の変数へ値を代入します。

④と⑤では小数を代入する

stay_hoursへ3.25を代入しています。

stay_hours = 3.25;

target_hoursへ5.50を代入しています。

target_hours = 5.50;

どちらも宣言後に値を設定しているため、初期化ではなく代入です。

代入が終わった後にprintfで表示しているため、安全に値を利用できます。

⑥ではchar型の変数を初期化する

area_rankはchar型です。

char area_rank = 66;

ASCIIでは、10進数の66が大文字のBに対応します。そのため、一般的なASCII互換環境で%cを使って表示するとBになります。

ただし、C言語の規格は、すべての処理系がASCIIを使用することまでは要求していません。Bという文字を保存することが目的なら、数値の66ではなく、文字定数を使うほうが意図が明確で移植しやすくなります。

char area_rank = 'B';

文字コードそのものを学習する場合は66、文字Bを保存する場合は'B'と使い分けるとよいでしょう。

初期化と代入を自然に使い分けよう

実際のプログラムでは、初期化と代入の両方を使います。

値が決まるタイミング適した書き方
宣言時に決まっている初期化する
キーボード入力で決まるscanfなどで設定する
計算後に決まる計算結果を代入する
条件によって変わる条件に応じて代入する
処理の途中で更新する新しい値を代入する

最初から値が決まっている場合は、次のように初期化します。

int count = 0;

後から値が決まる場合は、使用する前に代入します。

int count;

count = 5;

どちらを使う場合でも、共通しているのは、変数の値を読み取る前に有効な値を設定することです。

変数を宣言したら、printfや計算で使う場所までコードを追い、その途中で必ず初期化または代入されているか確認する習慣を付けましょう。