今回はstatic修飾子について話していきます。
変数や関数に”static”が付いていることがありますが、その意味と役割について図解をしながら説明していきます。
staticを知らないと、仕事でソースコードを見るときや書くときに間違いなく痛い目を見ることになりますので、しっかりおさえておきましょう!
また、staticの使いどころや注意点についても話していきますので、参考になればと思います。
static修飾子の意味は何か?
static修飾子を付けると、変数や関数の公開範囲は限定され、そのファイル内でしか参照出来なくなります。
「無駄に他のファイルに公開して、勝手に値を書き換えられるのを防ぐ」というのが、static修飾子を付けるポイントだと思っています。
だから現場でも、
上司「この変数って外部公開(extern)してるけど、XXXX.cでしか使ってないよね?それならstaticに変えておいて」
みたいな会話が出てきますので、staticを知っておかないといけません。
【補足】
externとは”外部公開”という意味合いで、extern修飾子を付けることで、他のファイルからも参照できるようになります。
始めて聞いた方は、後ででも良いので以下の記事を読んで、externの使い方やstaticとの違いをおさえておきましょう!
staticの使い方とその役割
static修飾子には、2つの使い方があります。使い方によって意味が変わってきますので、しっかり違いをおさえておきましょう!
- グローバル変数・関数にstaticを付ける
- ローカル変数(自動変数)にstaticを付ける
それぞれ解説していきます。
1. グローバル変数・関数にstaticを付ける
グローバル変数・関数の先頭にstaticを付けることで、公開範囲がファイル内に限定されます。これは冒頭でもお話しした通りですね。
ちなみに上記のソースコードをコンパイルすると、コンパイルエラーが発生します。
1 2 3 4 5 6 7 |
sub.c: In function ‘sub_func’: sub.c:5:5: error: ‘gloval_value’ undeclared (first use in this function) gloval_value = gloval_value + 10; ^~~~~~~~~~~~ sub.c:5:5: note: each undeclared identifier is reported only once for each function it appears in sub.c:6:5: warning: implicit declaration of function ‘calc’ [-Wimplicit-function-declaration] calc(); |
sub.cのstaticなグローバル変数(gloval_value)はmain.c内の変数を参照出来ませんし、calc関数もstatic化されていて参照出来ないためです。
staticを付けることで、他ファイルから意図せずに値を参照されたり書き換えられたりするのを防ぐことが出来るので、staticはソースコードの安全性を高めるためには必要なものです。
そのため、他から参照されたくないものや、同じファイル内でしか使用していない変数・関数があれば、staticを付けましょう!
2. ローカル変数(自動変数)にstaticを付ける
先ほどはグローバル変数にstaticを付けましたが、実はローカル変数(自動変数)にも付けることができます。ちなみに、ぼくはこの変数を「関数内static変数」と呼んでいます。
【補足】
先ほど説明した、グローバル変数にstaticが付く場合は、普通に「static変数」と呼ぶことが多いです。
なので、「static変数」と話題に出た場合は、基本的にグローバル変数の話をしていると思っておいて良いと思います。
関数内static変数には、以下の特徴があります。
- 関数の処理が終わっても値を保持し続ける
- なのでグローバル変数のように扱えるが、他の関数からは参照不可
普通、関数の自動変数は関数を抜けるとメモリが解放されてしまうため、値を保持し続けることができません。
しかし、staticを付けることで関数を抜けてもメモリを保持し続けるため、値も保持し続けることが出来ます。
簡単に言うと、グローバル変数のように扱うことが出来ます。
しかし、関数内static変数は文字の通り“関数内”の変数です。
なので、グローバル変数のような動きをしますが、他の関数から参照することができません。(コンパイルエラーになります)
あくまで公開範囲は関数内になりますので、ここは注意しましょう。
static変数・static関数はCファイルで定義する
グローバル変数や関数にstaticを付けると、定義したファイル内でしか参照出来ないことを話しました。
なので、static変数の定義やstaticな関数のプロトタイプ宣言は、ヘッダーファイルではなく、Cファイル内に記載してください。
static変数をヘッダーファイルに定義した場合
Cファイル内に定義したstatic変数の定義をヘッダーファイルに移動させると、実はコンパイルは通ります。
「え、じゃあヘッダーファイルでもいいんじゃないの?」
と思うかもしれませんが、別のCファイルがヘッダーファイルをインクルードしても、コンパイルは通ってしまいます。
本当はmain.c内でしか使用しないためstatic変数を定義しましたが、別のsub.cがmain.hをインクルードすると、sub.cでもstatic変数を使用することができます。
ただし、main.cで使用しているst_value(static変数)と、sub.cで使用しているst_value(static変数)はそれぞれ別のアドレスの変数なので、全くの別物です。
試しに、以下のソースコードを作成してみると、アドレスも格納されている値もそれぞれ異なっていることが分かります。
1 2 3 4 5 6 7 8 9 10 11 |
#include<stdio.h> #include"main.h" extern void sub_func(); //sub.cの関数を参照するため int main() { st_value = 0 ; printf("main.c():st_value...%d\n",st_value); printf("main.c():st_valueのアドレス...%p\n",&st_value); sub_func(); } |
1 2 3 4 5 6 7 8 9 |
#include<stdio.h> #include"main.h" void sub_func(void) { st_value = st_value + 10; printf("sub.c():st_value...%d\n",st_value); printf("sub.c():st_valueのアドレス...%p\n",&st_value); } |
1 2 |
/* static変数のプロトタイプ宣言 */ static int st_value ; |
1 2 3 4 |
main.c():st_value...0 main.c():st_valueのアドレス...0x7f6b7c801014 sub.c():st_value...10 sub.c():st_valueのアドレス...0x7f6b7c801018 |
まぁ実際にこんな感じで、static変数が色々なCファイルで使用されていたら、何か不具合が起きそうな気がしますね。。
また、せっかく外部に参照されないためにstatic変数にしたのに、結局他のファイルで参照しているのなら意味が無くなります。
要は、変なことになるので、static変数はCファイル内で定義するようにしましょう!
static関数をヘッダーファイルに定義した場合
static関数のプロトタイプ宣言をヘッダーファイルに移動するとどうなるのでしょうか?
これも先ほどのグローバル変数と同じで、他のファイルでもstatic関数を使用することが出来ます。
コンパイルは通りますが、違和感がすごくあるので、static関数のプロトタイプ宣言もCファイル内で書くようにしましょう。
関数内static変数の初期化は定義時にする
関数内static変数の初期化方法について説明します。
変数は何かしらの値を入れて初期化するのが基本です。変数の定義の後に値を入れる人もいれば、変数の定義と初期化を同時に行う人もいると思います。
1 2 3 4 5 6 7 8 9 |
int main() { int aaa; /* 変数の定義 */ int bbb = 0; /* 定義と初期化を同時に行う */ aaa = 0; /* 変数の定義の後に初期化 */ retun 0; } |
関数内static変数の場合は、変数の定義と初期化を同時に行ってください。定義と初期化を分けると、意図しない動作になる可能性があります。
上記画像のように関数内static変数の定義と初期化を分けると、関数がコールされる度に初期化の処理が動いてしまいます。
知らないとバグになってしまう可能性があるので、関数内static変数の初期化は注意してください。
最後に
staticはよく見かけますし、設計でもstatic化するかどうかは気にすることが多いと思います。
対となるexternと合わせて覚えておくと良いと思います!