C言語でプログラムを書いていると、同じ関数を何度も呼び出す場面はとても多いです。
そのとき「前に呼び出したときの値を、次の呼び出しまで覚えておきたい」と思うことがあります。
こうしたときに役立つのが静的変数(static変数)です。
本記事では、通常のローカル変数との違いや、メモリ上でどのように扱われているのかを、サンプルコードとともに初心者の方にも分かりやすく解説します。
C言語の静的変数(static変数)とは
静的変数の基本
C言語における静的変数(static変数)とは、プログラムの実行開始から終了まで、ずっと存在し続ける変数のことです。
特に関数内で宣言するstatic変数は、関数が呼び出されるたびに値が保持されるという特徴があります。
通常のローカル変数は関数を呼び出すたびに作られ、関数から戻ると消えますが、staticを付けることで次の呼び出しまで値を残しておくことができます。
代表的な特徴を文章で整理すると、静的変数は次のように説明できます。
- 関数内で宣言しても、その関数の処理が終わっても変数は消えない
- ただしスコープ(見える範囲)は宣言した関数の中だけ
- プログラム開始時に1回だけ確保・初期化される
このように、「寿命は長いが、見える範囲は狭い」変数だと理解するとイメージしやすくなります。
ローカル変数との違い
同じ関数内で使う変数でも、通常のローカル変数とstatic変数では性質が大きく異なります。
まずは両者の違いを整理しておくと、その後の理解がスムーズになります。
次の表では、関数内で宣言されるint a;とstatic int b;を比較しています。
| 項目 | 通常のローカル変数 | 関数内static変数 |
|---|---|---|
| 宣言例 | int a; | static int b; |
| 存在期間 | 関数の呼び出し中だけ | プログラム開始から終了まで |
| 初期化タイミング | 関数が呼ばれるたび | プログラム開始時に1回だけ |
| 初期値を指定しない場合 | 不定値(ゴミ値) | 0で初期化される |
| 値の保持 | 関数終了時に破棄 | 次の呼び出しまで保持される |
| スコープ(見える範囲) | 関数内だけ | 関数内だけ(同じ) |
スコープは同じでも、寿命が違うという点が最も大きなポイントです。
初心者の方は、まずこの点をしっかり意識しておくと混乱しにくくなります。
static指定で「一度だけ初期化される」仕組み
static変数には、「1回だけ初期化される」という重要な特徴があります。
これは、プログラムが開始されるときに、静的領域に変数が配置され、そのタイミングで初期化が行われるためです。
特に重要なのは次の2点です。
- 初期化式はプログラム全体で1度だけ評価される
- 関数を何度呼んでも、static変数は再初期化されない
例えば次のようなコードを考えます。
#include <stdio.h>
void func(void) {
static int count = 10; // この初期化はプログラム実行中に一度だけ
printf("count = %d\n", count);
count++;
}
int main(void) {
func(); // 1回目
func(); // 2回目
func(); // 3回目
return 0;
}
実行結果の一例は次のようになります。
count = 10
count = 11
count = 12
ここで重要なのは、static int count = 10;という初期化が最初の1回だけ行われ、それ以降は前回までの値を使い続けている点です。
もしこれが通常のローカル変数int count = 10;であれば、毎回10から始まり、値は増えません。
関数内static変数の書き方と挙動
関数内でのstaticの宣言方法と初期化ルール
関数内static変数は、書き方自体はとてもシンプルです。
通常のローカル変数の宣言にstaticを付けるだけです。
// 通常のローカル変数
int x;
// 関数内static変数
static int y;
宣言の基本ルールは次のように整理できます。
- 型名の前に
staticを書いて宣言する - 初期化値を指定しない場合、自動的に0で初期化される
- 初期化に使えるのは定数式(リテラルやコンパイル時に決まる値)のみ
例えば以下の宣言は有効です。
static int counter = 0;
static int flag = 1;
static char message[10] = "Hello";
一方、次のように関数の引数や他の変数を使って初期化することはできません。
void func(int n) {
// <mark style="background-color:rgba(0, 0, 0, 0);color:#cf2e2e" class="has-inline-color"><strong>エラー</strong></mark>: n は定数ではないためstatic変数の初期化に使えない
/* static int value = n; */
// OK: 定数を使った初期化
static int value = 100;
}
このように、static変数の初期化は「コンパイル時点で決まる値に限られる」という点を押さえておくと、コンパイルエラーを避けやすくなります。
複数回呼び出しで値がどう変化するか
関数内static変数の挙動を、通常のローカル変数と比較しながら確認してみます。
同じ関数を何度も呼び出したときに、変数がどう振る舞うかを理解すると、staticのイメージがつかみやすくなります。
次の例では、ローカル変数とstatic変数を両方使い、値の変化を観察します。
#include <stdio.h>
// 関数の宣言
void test(void);
void test(void) {
int local = 0; // 通常のローカル変数
static int s_local = 0; // 関数内static変数
local++; // 呼び出されるたびに 0 から 1 になる
s_local++; // 前回の値に 1 を足していく
printf("local = %d, s_local = %d\n", local, s_local);
}
int main(void) {
test(); // 1回目
test(); // 2回目
test(); // 3回目
return 0;
}
実行結果は次のようになります。
local = 1, s_local = 1
local = 1, s_local = 2
local = 1, s_local = 3
この結果から分かることをまとめると、次のようになります。
- localは毎回0からスタートし、1だけ増えて常に1
- s_localは前回の値を覚えていて、呼び出し回数に応じて1, 2, 3と増えていく
この挙動を「関数をまたいで値が保持される」と表現します。
静的変数を理解するうえで非常に大切なポイントです。
静的変数とメモリ領域
C言語では、変数は使われ方によって異なるメモリ領域に配置されます。
これを理解すると、なぜstatic変数が関数終了後も生き続けるのかをイメージしやすくなります。
一般的に、Cプログラムには次のようなメモリ領域があります。
| 領域名 | 主に置かれるもの | 特徴 |
|---|---|---|
| 静的領域(データ領域) | グローバル変数, static変数 | プログラム開始から終了まで存在 |
| スタック領域 | 関数のローカル変数, 引数 | 関数の呼び出しごとに確保・解放 |
| ヒープ領域 | mallocなどで確保したメモリ | 手動で確保・解放する必要がある |
通常のローカル変数はスタック領域に置かれ、関数が呼ばれるたびに作られ、戻るときに消えます。
一方で、static変数は静的領域(データ領域)に置かれ、プログラムの実行中はずっとそのままです。
このため、関数が終わってもstatic変数の値は残り続け、次の呼び出しでも同じメモリ上の値にアクセスできる、という仕組みになっています。
「ローカル変数はスタック、static変数は静的領域」という対応関係を、頭の片隅に置いておくと良いです。
例で学ぶstatic変数の使いどころ
ローカル変数との違いを確認するサンプルコード
ここでは、staticあり・なしでどのように動作が変わるのかを、同じような関数で比較してみます。
まずは2種類の関数を用意します。
#include <stdio.h>
// 通常のローカル変数を使う関数
void normal_counter(void) {
int count = 0; // 毎回 0 に初期化される
count++;
printf("[normal ] count = %d\n", count);
}
// static変数を使う関数
void static_counter(void) {
static int count = 0; // 最初の1回だけ 0 に初期化される
count++;
printf("[static ] count = %d\n", count);
}
int main(void) {
int i;
printf("=== normal_counter ===\n");
for (i = 0; i < 3; i++) {
normal_counter();
}
printf("=== static_counter ===\n");
for (i = 0; i < 3; i++) {
static_counter();
}
return 0;
}
想定される実行結果は次のとおりです。
=== normal_counter ===
[normal ] count = 1
[normal ] count = 1
[normal ] count = 1
=== static_counter ===
[static ] count = 1
[static ] count = 2
[static ] count = 3
この例から、「通常のローカル変数は毎回リセットされる」「static変数は値が蓄積される」という違いを、実際の動作として確認できます。
静的変数で作る「カウンタ関数」の実装例
static変数の代表的な使い方として、関数の呼び出し回数を数えるカウンタがあります。
これは実務でも非常によく使われるパターンです。
次のコードでは、get_call_countという関数を呼び出すたびに、回数を増やして返すようにしています。
#include <stdio.h>
// 呼び出されるたびにカウントアップして、その値を返す関数
int get_call_count(void) {
// この変数はプログラム終了まで生き続ける
static int count = 0;
count++; // 前回の値に 1 を足す
return count; // 現在のカウントを返す
}
int main(void) {
printf("1回目: %d\n", get_call_count());
printf("2回目: %d\n", get_call_count());
printf("3回目: %d\n", get_call_count());
return 0;
}
実行結果の一例は次のようになります。
1回目: 1
2回目: 2
3回目: 3
このように、static変数を使うことで、関数の内部に「小さな状態」を持たせることができます。
呼び出し元からは変数自体は見えず、get_call_countが返す値だけが見える形になるため、情報隠蔽の観点でもメリットがあります。
グローバル変数を使わない状態管理の書き方
「関数の外からも見える変数」としてグローバル変数がありますが、初心者のうちからグローバル変数に頼りすぎると、プログラムの見通しが悪くなりがちです。
static変数をうまく使うと、グローバル変数を使わずに状態を管理しやすくなります。
例えば「簡単な乱数もどき」を作る例を考えてみます。
#include <stdio.h>
// 非常に単純な「擬似乱数のようなもの」を返す関数
int simple_random(void) {
// シード値を保持するstatic変数
static unsigned int seed = 12345; // 初期値は適当な固定値
// 線形合同法のような簡単な更新式
seed = seed * 1103515245 + 12345;
// 上位ビットを落として、0〜32767程度の範囲にする
return (int)((seed >> 16) & 0x7FFF);
}
int main(void) {
int i;
for (i = 0; i < 5; i++) {
printf("%d\n", simple_random());
}
return 0;
}
実行するたびにまったく同じ値の並びにはなりますが、呼び出すたびに違う値が得られます。
ここでポイントになるのは、乱数のシードseedをグローバル変数にせず、関数内static変数として隠している点です。
この書き方の利点は、次のようにまとめられます。
- 外部から
seedに直接アクセスできないので、意図しない書き換えを防げる - 関数だけを見れば「この関数が自分で状態を管理している」ことが分かる
- グローバル変数だらけになるのを防げる
「外から見えないけれど、関数の中では状態を保持したい」という場面では、関数内static変数がとても有効です。
デバッグ用の回数カウント・ログ出力への応用
実開発では、デバッグのために関数の呼び出し回数を表示したいことがあります。
このときにも、static変数は便利に使えます。
次の例では、ある処理をログ付きで実行し、その際に呼び出し回数を出力しています。
#include <stdio.h>
// デバッグ用の処理関数
void debug_process(const char *msg) {
// この関数が何回呼ばれたかを数える
static int call_count = 0;
call_count++; // 呼び出し回数をカウントアップ
printf("[DEBUG] %d回目の呼び出し: %s\n", call_count, msg);
// ここに本来の処理を書く
// ...
}
int main(void) {
debug_process("初期化処理");
debug_process("メインループ1回目");
debug_process("メインループ2回目");
return 0;
}
想定される実行結果は次のとおりです。
[DEBUG] 1回目の呼び出し: 初期化処理
[DEBUG] 2回目の呼び出し: メインループ1回目
[DEBUG] 3回目の呼び出し: メインループ2回目
このように、デバッグやログ出力のときに「今何回目か」を簡単に表示できるのはstatic変数の利点です。
特に、バグが出たときに「何回目の処理でおかしくなったのか」を調べる手がかりにもなります。
static変数を使うときの注意点と設計のコツ
グローバル変数とstatic変数の使い分け
グローバル変数とstatic変数は、どちらも静的領域に置かれ、プログラム中ずっと存在するという共通点があります。
しかし、「どこから見えるか」という点では大きく異なります。
グローバル変数は、宣言されたファイルだけでなく、他のファイルからも参照できます。
一方で、関数内static変数は、その関数からしか見えません。
つまり次のように整理できます。
- グローバル変数: プログラム全体からアクセス可能
- 関数内static変数: その関数の内部からだけアクセス可能
設計のコツとしては、次の考え方が役立ちます。
- 本当に全体から共有したい情報だけをグローバル変数にする
- 特定の関数だけが使う情報は、その関数のstatic変数で閉じ込める
このように、「見えないほうが安全なものは、なるべくスコープを狭くする」という方針で設計すると、バグの少ないコードになりやすくなります。
静的変数のデメリット
便利なstatic変数ですが、使いすぎると問題が出てくることもあります。
主なデメリットを文章で説明します。
まず、static変数はプログラムが終了するまでメモリを占有し続けるため、一時的なデータには向きません。
スタックに置かれるローカル変数なら、関数が終わればメモリが解放されますが、static変数はそうはいきません。
さらに、static変数は関数内部に隠れた「状態」を持つため、プログラムの流れを追いにくくなることがあります。
同じ関数を呼んでいるのに、呼び出す順番や回数によって結果が変わるため、バグの原因が見つけにくくなる場合があります。
特に次のような状況では注意が必要です。
- static変数を多用していて、どこでどの値になっているか把握しにくい
- テストのたびにstatic変数をリセットしたいが、簡単に初期化できない
- 並行処理(スレッドなど)で複数箇所から同じstatic変数を触ってしまう
「とりあえずstaticで値を残しておけば便利そうだ」という感覚で乱用すると、後から自分が困ることにつながりやすいので、必要な場面を選んで使うことが大切です。
初心者がやりがちなstaticのミスと防ぎ方
最後に、C言語初心者がstatic変数でつまずきやすいポイントと、その防ぎ方をまとめます。
ここでは関数内static変数に絞って説明します。
ミス1: 初期化されるタイミングを勘違いする
「関数が呼ばれるたびに初期化される」と思い込んでしまうミスがよくあります。
static変数はプログラム開始時に1回だけ初期化されます。
防ぎ方としては、関数にコメントを書いておくとよいです。
void func(void) {
// プログラム開始時に1回だけ初期化される
static int value = 0;
// ここでは再初期化されない
value++;
}
このように「いつ初期化されるのか」を明示しておくと、自分も他人も誤解しづらくなります。
ミス2: 初期化に変数や関数の結果を使おうとする
先ほども触れましたが、static変数の初期化には定数式しか使えません。
次のようなコードはコンパイルエラーになります。
int get_default(void) {
return 10;
}
void func(void) {
// <mark style="background-color:rgba(0, 0, 0, 0);color:#cf2e2e" class="has-inline-color"><strong>コンパイルエラーになる例</strong></mark>
/* static int value = get_default(); */
// 防ぎ方: 定数で初期化しておき、最初の呼び出し時に必要なら処理を行う
static int value = 0;
static int initialized = 0;
if (!initialized) {
value = get_default(); // 最初の1回だけ実行したい処理
initialized = 1;
}
// ここから value を使う
}
このように「初回だけ実行したい処理」がある場合は、別のstatic変数をフラグとして使うというテクニックがよく使われます。
ミス3: 「なぜか値がリセットされない」と混乱する
通常のローカル変数のつもりでstaticを付けてしまい、「printfの値がどんどん増えていく」と戸惑うケースもよくあります。
これは、staticを付けると寿命が変わることを忘れているために起こります。
防ぎ方としては、本当に必要なときだけstaticを使うという習慣を付けることです。
「値を次回の呼び出しまで保持したいか」を自分に問いかけ、そうでなければstaticを付けない、というルールで書くとミスが減ります。
まとめ
静的変数(static変数)は、「関数内で宣言しても、プログラム終了まで値を保持し続ける変数」です。
通常のローカル変数とは異なり、プログラム開始時に1度だけ初期化され、関数の呼び出しをまたいで値が残るという特徴があります。
カウンタ関数や簡単な状態管理、デバッグ用の呼び出し回数カウントなど、便利な使い道が多くありますが、使いすぎるとコードが追いにくくなるというデメリットもあります。
初心者のうちは、「値を次の呼び出しまで覚えておく必要があるときだけ使う」と決めておくと、static変数を安全かつ効果的に活用しやすくなります。
