このページでは static
変数、特に関数内で変数宣言される static
ローカル変数について解説していきたいと思います。
まず static
には下記の2つの役割があります。
- 変数を静的領域に配置する
- 変数や関数の公開範囲を制限する
前者はローカル変数に対して static
を付加した時の static
の役割になります。
一方で後者は、グローバル変数や関数に対して static
を付加した時の static
の役割になります。
これらの役割は全く異なるものになります。
本ページでは、前者の方、すなわち、ローカル変数に対して static
を付加した static
ローカル変数について解説していきます。
static
関数や static
グローバル変数については下記ページで解説していますので、こちらの詳細を知りたい方は下記ページを参照していただければと思います。
ローカル変数という言葉を聞き慣れない方も多いかもしれませんが、要は「関数内で宣言する変数」のことを言っています。
主にグローバル変数と区別するためにこのような言い方をします。
ローカル変数は宣言した関数内でのみしか使用できませんが、グローバル変数はどの関数からも使用することができます。
このローカル変数とグローバル変数では static
する効果が大きく異なるため、このページではこの2つを区別するために、この呼び方をさせていただきます(グローバル変数の static
化のメリットは上記ページで解説しています)。
グローバル変数の static
化は、確か他のソースコードに “非公開” にするためのものだよね
でもローカル変数は元々他のソースコードには非公開だし、そもそも他の関数に対しても非公開だよね…
なんで static
つける意味あるんだろ…
公開範囲の観点だと、ローカル変数を static
化する意味は無いよ
だけど他の全く異なる効果がある!
その辺りを解説していくよ
Contents
static
ローカル変数の宣言
static
ローカル変数を宣言するためには、通常の変数宣言の先頭に static
を付加するようにすれば良いです。
例えば int
型の変数 x
を static
ローカル変数として宣言するのであれば、関数内に下記を記述します。
static int x;
もし static
を付けなかった場合は、通常のローカル変数として宣言することになります。
では、ローカル変数と static
ローカル変数ではどのような違いがあるのでしょうか?この点についてここから解説していきたいと思います。
ローカル変数を static
化する効果
ではローカル変数をわざわざ static
ローカル変数として宣言することで、どのようにプログラムでの扱われ方が異なるのか?について解説していきたいと思います。
思い出した!
単なるローカル変数と static
ローカル変数では、確か変数の生存期間が違うんだよね
その通り!
でもそれはなぜ?
おそらく多くの方が、ローカル変数と static
ローカル変数では、下記のように生存期間が異なることをご存知なのでは無いかと思います。
- ローカル変数:関数開始から終了まで
static
ローカル変数:プログラム開始から終了まで
ではこれはなぜでしょう?
この理由が分かるように、もう一段掘り下げた解説をしていきたいと思います。
スポンサーリンク
メモリの配置先が変わる
まず前提として、変数は変数宣言時にメモリ上に配置されます。
要は、「変数 = メモリの一部分」です。変数を利用してメモリに値を格納したりメモリから値を取得しているだけです。
で、ローカル変数と static
ローカル変数ではこのメモリの配置先が下記のように異なります。
- ローカル変数:スタック領域
static
ローカル変数:静的領域
そして、この配置先が異なることを理由に、ローカル変数と static
ローカル変数とではプログラムでの扱われ方が大きく異なります。
“静的領域” に配置される効果
では、変数が “静的領域” に配置された場合と “スタック領域” に配置された場合とでどのように変数の扱われ方が異なるのでしょうか?
この点について解説していきたいと思います。
静的領域に配置されるのでプログラム実行中はずっと生存
前述の通り、ローカル変数と static
ローカル変数とでは下記のように生存期間が異なります。
- ローカル変数:関数開始から終了まで
static
ローカル変数:プログラム開始から終了まで
これは、ローカル変数が配置される “スタック領域” がいろんな変数で使い回しされるメモリであるのに対し、static
ローカル変数が配置される “静的領域” が各変数専用のメモリであるためです。
“スタック領域” はいろんな変数で使い回しされるメモリ
ローカル変数は宣言時に「スタック領域」に配置されます。
スタック領域は、要はプログラム内でいろんな変数用に「使い回しされるメモリ領域」です。
スタック領域は、その名の通りスタックのように扱われるメモリ領域です
ですが、今回は単なるローカル変数と static
ローカル変数との違いに焦点を当てたいので、このスタックについては今回触れず、メモリの使い回しの観点で解説させていただきます
前述の通り、関数内で宣言された変数はメモリ上に配置されます。このとき、このメモリは “一時的に” この変数専用のメモリになります。
ただし、専用のメモリになっているのは、変数宣言を行なった関数が終了するまでの間だけです。関数が終了すれば、そのメモリは専用のメモリでなくなります。
専用のメモリで無くなるので、次に他の関数が実行されたような時には、その関数で変数宣言が行われるとその変数がそのメモリ上に配置される可能性があります。
つまり、スタック領域はプログラム内でいろんな変数を配置するために「使い回しされるメモリ領域」になります。
この動作は下記のようなプログラムで各変数が配置されたアドレスを表示してみると分かりやすいです。
#include <stdio.h>
int add(int a, int b) {
int c;
printf("add:\n");
printf(" &a = %p\n", &a);
printf(" &b = %p\n", &b);
printf(" &c = %p\n", &c);
c = a + b;
return c;
}
int sub(int d, int e) {
int f;
printf("sub:\n");
printf(" &d = %p\n", &d);
printf(" &e = %p\n", &e);
printf(" &f = %p\n", &f);
f = d - e;
return f;
}
int main(void) {
int x, y, ans;
x = 100;
y = 50;
ans = add(x, y);
x = 200;
y = 30;
ans = sub(x, y);
return 0;
}
関数内で使用する変数のアドレスを表示するようにしており、実行すると私の環境では下記のような結果が表示されました。
add: &a = 0x7ffee5461a3c &b = 0x7ffee5461a38 &c = 0x7ffee5461a34 sub: &d = 0x7ffee5461a3c &e = 0x7ffee5461a38 &f = 0x7ffee5461a34
ポイントは、下記のように add
関数と sub
関数の変数のアドレスが一致しているところです。
- 変数
a
と変数d
- 変数
b
と変数e
- 変数
c
と変数f
要は、add
関数の変数のメモリの配置先は、sub
関数実行時には sub
関数の変数のメモリの配置先として使い回しされていることになります。
こんな感じで、関数内で宣言した変数の配置先のメモリは、その関数終了後は他の変数で使い回しされる可能性があるので、実質的にローカル変数の生存期間は「関数開始から関数終了」までとなります。
ちなみにスタック領域にはローカル変数だけでなくレジスタの値などを格納するのにも利用されます。
“静的領域” は各変数専用のメモリ
単なるローカル変数が “スタック領域” に配置されるのに対して、static
ローカル変数は “静的領域” に配置されます。
スタック領域がいろんな変数用に「使い回しされるメモリ領域」であるのに対して、静的領域は各変数の「専用のメモリ領域」になります。
さらに、static
ローカル変数は関数実行時ではなく、プログラム開始時にメモリに配置されます。
そして、変数が静的領域に配置されれば、その配置された位置のメモリはプログラム開始からプログラム終了までの間、ずっとその変数専用のメモリになります。
つまり、静的領域のメモリは他の変数に使い回しされるようなことはありません。なので、静的領域に配置される static
ローカル変数の生存期間は「プログラム開始からプログラム終了まで」となります。
例えばローカル変数の配置先は “スタック領域”で紹介したプログラムにおいて、関数 add
で宣言する変数 c
を static
ローカル変数にしてみましょう。
#include <stdio.h>
int add(int a, int b) {
static int c;
printf("add:\n");
printf(" &a = %p\n", &a);
printf(" &b = %p\n", &b);
printf(" &c = %p\n", &c);
c = a + b;
return c;
}
int sub(int d, int e) {
int f;
printf("sub:\n");
printf(" &d = %p\n", &d);
printf(" &e = %p\n", &e);
printf(" &f = %p\n", &f);
f = d - e;
return f;
}
int main(void) {
int x, y, ans;
x = 100;
y = 50;
ans = add(x, y);
x = 200;
y = 30;
ans = sub(x, y);
return 0;
}
私の環境で実行すると、表示結果は下記のように変化しました。
add: &a = 0x7ffee18b5a3c &b = 0x7ffee18b5a38 &c = 0x10d94c020 sub: &d = 0x7ffee18b5a3c &e = 0x7ffee18b5a38 &f = 0x7ffee18b5a34
ポイントは変数 c
のアドレスですね!
ローカル変数の配置先は “スタック領域”で紹介したプログラムの場合は、変数 c
と変数 f
のアドレスが一致していました。つまり、変数 c
が配置されたメモリは変数 f
で使い回しされていたことになります。
一方、今回の結果では変数 c
はどの変数ともアドレスが一致していません。これが static
化した効果で、変数 c
は静的領域の変数 c
専用のメモリに配置されているため、どの変数ともアドレスが一致することはありません。
ちなみにグローバル変数もこの静的領域に配置される変数になります。なので、グローバル変数の生存期間はプログラム開始からプログラム終了の間であり、この間であればファイル内のどの関数からもこの変数を使用することができるのです。
ですが、そうなると、グローバル変数に static
を付ける意味はあるのでしょうか?
意味はあります。static
の有無によってグローバル変数の公開範囲が変わります。
これについては下記ページで解説していますので、興味のある方は是非読んでみてください!
static 関数と static グローバル変数の使い方・メリットを解説スポンサーリンク
静的領域に配置されるので大きなサイズの変数が扱える
また、単なるローカル変数と static
ローカル変数とでは、宣言可能な変数のサイズも異なります。
この違いがあるのも、ローカル変数と static
ローカル変数の配置先が異なることが理由になります。
スタック領域のサイズ < 静的領域のサイズ
前述の通り、ローカル変数は宣言すると “スタック領域” に、static
ローカル変数は “静的領域” に配置されます。
ここで重要になるのが、各領域のサイズです。配置される変数のサイズがこの領域のサイズを超えてしまうと実行時にエラーになります(スタックオーバーフロー・セグメンテーションフォールトなど)。
また、基本的にスタック領域のサイズよりも静的領域として使用できるサイズの方が大きいです。
スタック領域のサイズは、特に設定変更していなければ数 MB 程度です
私の PC だとスタック領域のサイズは 8
MB に設定されています
一方で、静的領域は、静的領域に配置する変数のサイズに応じて変わります
ただし、静的領域はプログラム実行時に PC 上の空いているメモリから割り当てられるため、他のアプリがメモリをたくさん使用しているような場合は静的領域が確保できない可能性もあります
ですので、単なるローカル変数よりも static
ローカル変数の方が大きなサイズの変数を宣言することが可能です。
このため、ローカル変数で宣言するとエラーになるような巨大なサイズの配列も、static
化するだけでそのエラーを解消するようなことも可能です。
例えば、下記の 10
MB の配列 array
を変数宣言するプログラムについて動作を確認してみましょう!
int main(void) {
char array[10*1024*1024];
return 0;
}
私の環境でこのプログラムを実行すると “segmentation fault” エラーが発生します。
これは、スタック領域のサイズを超えた変数をスタック領域に配置したために発生したエラーです(環境によってはスタックオーバーフローエラーになるかもしれないです)。
私の環境ではスタックサイズが 8
MB に設定されているため、このサイズを超えた変数を配置しようとすると上記のエラーが発生します。
一方で、変数 array
の宣言時に static
を付加すれば、プログラムが正常終了するようになります。
int main(void) {
static char array[10*1024*1024];
return 0;
}
これは、static
ローカル変数として array
を宣言することで、array
が静的領域に配置されるようになったためです(前述の通り基本的にスタック領域よりも静的領域の方が大きい)。
こんな感じで、static
ローカル変数が静的領域に配置されることを利用して巨大なサイズの変数を宣言できるようにすることも可能です。
私は画像処理プログラミングなどを行うときは結構この配列宣言時にエラーになる現象に頭を悩まされていました…(malloc
でメモリ確保することで対応してました)。
まとめ
このページでは static
ローカル変数について解説しました!
単なるローカル変数と static
ローカル変数とでは、変数が配置されるメモリ領域が下記のように異なります。
- ローカル変数:スタック領域
static
ローカル変数:静的領域
そして、この配置されるメモリ領域の違いによって、単なるローカル変数と static
ローカル変数と下記がで異なります。
- 生存期間
- 宣言できる変数のサイズ
特にポイントは「生存期間」の違いだと思います。この「生存期間」が異なることで、メリットもありますがデメリットもあります。
例えば static
ローカル変数では、マルチスレッドでこの変数にアクセスする場合は排他制御が必要になります。
これについては下記ページで解説していますので、マルチスレッドプログラミングに興味のある方は参考にしていただければと思います。
【C言語】排他制御について解説【Mutex】static
を付加することでどのような違いが発生するかを理解した上で、プログラミング時に「単なるローカル変数と static
ローカル変数」をしっかり使い分けするのが重要です。
是非このページで解説したことを参考に、単なるローカル変数と static
ローカル変数を使い分けしたプログラミングを行なっていただければ幸いです。
オススメの参考書(PR)
C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!
まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。
- 参考書によって、解説の仕方は異なる
- 読み手によって、理解しやすい解説の仕方は異なる
ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?
それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。
なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。
特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。
もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!
入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。
https://daeudaeu.com/c_reference_book/