このページでは C 言語における static 関数(静的関数)と static グローバル変数(静的グローバル変数)の使い方、これらを使用するメリットについて解説します。
一応 static の使い方は習ったんだけど使ったことないなぁ
メリットがよく分からない…
C 言語を始めたての方には特にそういう人は多いと思うよ
static は複数人でプログラム開発してるときに特にメリットの大きい修飾子なんだ
確かに今は独学で勉強してるからなぁ…
static は特に会社で大人数で開発するときなんかは必須になるよ
なるほど!
いつか役に立つ知識になるから、今は使わなくてもメリットだけでも覚えておくいいよ!
まずは使い方を軽く説明して、その次にメリットを詳しく説明するよ!
Contents
static 関数
まずは static 関数の使い方と、関数を static にする効果について解説します。
static 関数の使い方
では使い方について簡単に解説していきます。
まずは通常の(static でない)関数のおさらい。通常の関数は下記のように定義を行います。
返却値の型 関数名(引数1の型 引数1の変数名, 引数2の型 引数1の変数名) {
関数の処理
}
例えば、int 型の変数2つを引数とし、その2つを足し合わせた結果を int 型で返却する add 関数は下記のように定義できます。
int add(int x, int y) {
return x + y;
}
これに対し、static 関数では関数定義の先頭(つまり 返却値の型の前)に「static」という修飾子を記述します。
static 返却値の型 関数名(引数1の型 引数1の変数名, 引数2の型 引数1の変数名) {
関数の処理
}
上記の add 関数を static 関数として扱うのであれば、前述の通り関数定義の先頭に「static」を記述すれば良いです。
static int add(int x, int y) {
return x + y;
}
スポンサーリンク
関数を static にする効果
続いては関数を static にする効果です。
通常の関数は、その関数の定義を行ったソースコードファイル内だけでなく、他のソースコードファイルからも参照できる状態にあります。つまり通常の関数は他のソースコードファイルからも呼び出し可能です。
このような関数は公開関数などと呼ばれます。
一方で、static 関数は、その関数定義を行ったソースコードファイル内からしか参照できません。つまり static 関数は他のソースコードファイルからは呼び出しすることが不可能です。
このような関数は非公開関数と呼ばれます。
つまり、関数を static にする効果は「他のソースコードファイルからの関数呼び出しを制限すること」と言えます。
static グローバル変数
次は static グローバル関数の使い方と、グローバル変数を static にする効果について解説します。
static グローバル変数の使い方
グローバル変数自体は、関数等のブロックの外で変数宣言することで作成することが可能です。ファイルの先頭付近で宣言するのが一般的ですね。
変数の型 変数名;
int main(void){
・・・・
}
例えば int 型の x という変数をグローバル変数として扱うのであれば、下記のように記述すれば良いです。
int x;
int main(void){
・・・・
}
これにより、ファイル内のどこからでもこの変数を参照することが可能になります。便利です。
static グローバル変数についてもグローバル変数と宣言する場所は同じです。関数外で変数宣言を行います。ただし、static グローバル変数の場合は変数の型の前に「static」という修飾子を記述します。
static 変数の型 変数名;
int main(void){
・・・・
}
例えば int 型の x という変数を static グローバル変数として扱うのであれば、下記のように記述すれば良いです。
static int x;
int main(void){
・・・・
}
スポンサーリンク
グローバル変数を static にする効果
続いてはグローバル変数を static にする効果の説明です。この効果は前述の関数を static にする効果とほぼ同様です。
通常のグローバル変数は、そのグローバル変数の宣言を行ったソースコードファイル内だけでなく、実は他のソースコードファイルからも参照できる状態にあります。つまり通常のグローバル変数は他のソースコードファイルからも使用可能です。
あまり使用しませんが、ここでは便宜上、このような変数を公開グローバル変数と呼びます。
一方で、static グローバル変数は、その変数宣言を行ったソースコードファイル内からしか参照できません。つまり static グローバル変数は他のソースコードファイルからは使用することが不可能です。
こちらは先程と同様に、便宜上、非公開グローバル変数と呼びます。
つまり、グローバル変数を static にする効果は「他のソースコードファイルからの使用を制限すること」と言えます。
他にも関数内でのみ使用可能なローカル変数というものも存在します。ややこしいのでそれぞれの変数の有効範囲(スコープ)を下の表にまとめておきます。
変数の種類 | 有効範囲 |
ローカル変数 | ブロック内 |
静的グローバル変数 | ファイル内 |
グローバル変数 | ファイル内 (リンクする)他のファイル |
static 関数と static グローバル変数のメリット
特にこの2つが活躍するのが、複数人で同時に複数のソースコードファイルを編集しながら開発するときです。
前述の通り、関数やグローバル変数を static 化することで、その関数やグローバル変数が使用可能な範囲を、定義・宣言したファイル内に制限することができます。
これにより、下記のメリットがあります。
- 複数ファイル間での名前の衝突が防げる
- 変更時の影響範囲を局所化できる
それぞれについて実例を用いて説明していきます。
複数ファイル間での名前の衝突が防げる
C言語においては、リンクし合うソースコード(正確に言うとリンクし合うオブジェクト)内で、同じ名前の公開関数や公開グローバル変数を使用できないというルールがあります。
同じ名前の関数があると、どの関数が呼び出されているか分かりませんもんね。変数も同様です。
例えば下記の main.c と sub.c をコンパイル&リンクしてみましょう!
#include <stdio.h>
/* 関数のextern宣言 */
/* sub.cで定義されるcalc() */
extern int calc(int);
/* 関数のプロトタイプ宣言 */
void test(void);
/* グローバル変数の宣言 */
int a = 0;
/* 関数の定義 */
void test(void){
a++;
printf("main.cのtest()が呼ばれるのは%d回目です\n", a);
}
int main(void){
int val;
int ans;
printf("値を入力してください:");
scanf("%d", &val);
test();
ans = calc(val);
printf("計算結果は%dです\n", ans);
return 0;
}
#include <stdio.h>
/* 関数のプロトタイプ宣言 */
int calc(int);
void test(void);
/* グローバル変数の宣言 */
int a = 0;
/* 関数の定義 */
int calc(int x){
test();
return x * 4;
}
void test(void){
a++;
printf("sub.cのtest()が呼ばれるのは%d回目です\n", a);
}
エラーを紹介することが目的なので、特にプログラム自体に意味はないです。ポイントは「両方のファイルで関数 test とグローバル変数 a を定義・宣言している」ところです。
また下記の extern は他のファイルで定義される公開関数を使用するための宣言になります。普通は他のファイルで使用しても良い公開関数は明示的にヘッダーでプロトタイプ宣言したりしますが、それが無くても extern を使えば公開関数は他のファイルから使用できてしまいます。
extern int calc(int);
コマンドラインから下記コマンドでコンパイル・リンクしてみます。
$ gcc main.c sub.c -o main.exe
すると、下記のようにエラーが発生してしまいます(環境によってエラーメッセージは異なると思います)。
$ gcc main.c sub.c -o main.exe
duplicate symbol _test in:
/var/folders/l5/88ztxzgs1p9ckqq9rzn3b24h0000gn/T/main-3367e5.o
/var/folders/l5/88ztxzgs1p9ckqq9rzn3b24h0000gn/T/sub-54e036.o
duplicate symbol _a in:
/var/folders/l5/88ztxzgs1p9ckqq9rzn3b24h0000gn/T/main-3367e5.o
/var/folders/l5/88ztxzgs1p9ckqq9rzn3b24h0000gn/T/sub-54e036.o
ld: 2 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
このエラーはまさに、ここで説明した名前の衝突です。
公開関数や公開グローバル変数に、気軽にありきたりな名前をつけてしまうと、同時に開発を行なっている方が作成した関数やグローバル変数と名前が被ってリンクが通らなくなる可能性があり、チームに迷惑をかけてしまう可能性もあります。
このような名前の衝突を防ぐためには、他の人と被らないような関数名や変数名を考える必要があります。
しかし、static を使用すると、関数は非公開関数に、グローバル変数は非公開グローバル変数となります。つまり static を付与することにより、他のファイルから参照不可能になり、他のソースコードファイルの関数や変数と名前が被っても、名前の衝突が起こりません。
例えば上記の main.c を下記のように変更してみましょう。
#include <stdio.h>
/* 関数のextern宣言 */
/* sub.cで定義されるcalc() */
extern int calc(int);
/* 関数のプロトタイプ宣言 */
static void test(void);
/* グローバル変数の宣言 */
static int a = 0;
/* 関数の定義 */
static void test(void){
a++;
printf("main.cのtest()が呼ばれるのは%d回目です\n", a);
}
int main(void){
int val;
int ans;
printf("値を入力してください:");
scanf("%d", &val);
test();
ans = calc(val);
printf("計算結果は%dです\n", ans);
return 0;
}
これにより main.c の関数 test と グローバル変数 a は非公開となります。ですので名前の衝突が解決され、リンクの失敗が発生しなくなって実行ファイル生成ができるようになります。
$ ./main.exe 値を入力してください:5 main.cのtest()が呼ばれるのは1回目です sub.cのtest()が呼ばれるのは1回目です 計算結果は20です
このように、static は他のファイルとの名前の衝突を防ぐのに便利です。特に複数人で開発するときにはメリットがあります。
スポンサーリンク
変更時の影響範囲を局所化できる
もう一つのメリットは変更時の影響範囲を局所化できることです。
前述の通り公開関数や公開グローバル変数は他のソースコードファイルから使用可能です。これはつまり、公開関数や公開グローバル変数は他のソースコードファイルから使用されている可能性があるということになります。
したがって、公開関数や公開グローバル変数を変更すると、その変更の影響をいろんなソースコードファイルに影響を与える可能性が高いです。
今度は下記の main.c と sub.c について考えてみましよう。
#include <stdio.h>
/* 関数のextern宣言 */
/* sub.cで定義されるcalc() */
extern int calc(int);
/* 関数のプロトタイプ宣言 */
static void test(void);
/* グローバル変数の宣言 */
static int a = 0;
/* 関数の定義 */
static void test(void){
a++;
printf("main.cのtest()が呼ばれるのは%d回目です\n", a);
}
int main(void){
int val;
int ans;
printf("値を入力してください:");
scanf("%d", &val);
test();
ans = calc(val);
printf("計算結果は%dです\n", ans);
return 0;
}
#include <stdio.h>
/* 関数のプロトタイプ宣言 */
int calc(int);
void test(void);
/* グローバル変数の宣言 */
int a = 0;
/* 関数の定義 */
int calc(int x){
test();
return x * 4;
}
void test(void){
a++;
printf("sub.cのtest()が呼ばれるのは%d回目です\n", a);
}
main.c が sub.c で定義される関数 calc を使用しています。この状態だとリンクも成功します。
sub.c を下記のように変更してみましよう。
#include
/* 関数のプロトタイプ宣言 */
int calc(int, int);
void test(void);
/* グローバル変数の宣言 */
int a = 0;
/* 関数の定義 */
int calc(int x, int y){
test();
return x * y;
}
void test(void){
a++;
printf("sub.cのtest()が呼ばれるのは%d回目です\n", a);
}
calc の引数を int 型1つから int 型2つに変更しています。
使用するコンパイラによって動作は違うかもしれませんが、この状態でも私の環境ではリンクエラーは発生せず、実行ファイルは作成できてしまいました。
しかし実行してみると下記のような結果になりました。明らかに計算結果がおかしいですね…。
$ ./main.exe 値を入力してください:5 main.cのtest()が呼ばれるのは1回目です sub.cのtest()が呼ばれるのは1回目です 計算結果は369480です
これは main.c が参照しようとしている calc 関数と、実際に sub.c で定義されている calc 関数に食い違いがあるためです(おそらく main.c から calc 関数呼び出し時に2つ目の引数を指定できていないので、sub.c の calc 関数では2つ目の引数としては初期値(つまり不定値)を受け取って処理しているので、このようなおかしな計算結果になっているのだと思います)。
このように、関数は変更してしまうと呼び出し元に影響を及ぼします。おそらく返却値の型や引数が変わるとコンパイラによってはリンク時にエラーが出ると思います。
またエラーが発生しなかったとしても、関数に必要な引数が渡せなくなったりしますので、思った通りの計算結果が得られなくなってしまいます。つまり、公開関数はうまく変更してやらないと、関数呼び出し側が期待した動きでなくなり、プログラム全体として上手く動作しなくなる可能性があります。そうなると一緒に開発している人に迷惑をかける可能性もあります。
特に公開関数の場合、いろんなファイルから使用されている可能性があるので、いろんなファイルに影響を及ぼす可能性があります。
しかし、static 関数の場合は非公開ですので、他のファイルから使用されることはありません。ですので、static 関数を変更したとしても影響を及ぼすのはその関数が定義されているファイル内のみです。
つまり、static を付加することで、その関数の変更による影響範囲をその関数を定義するファイル内に局所化することができます。
なので、static 関数の場合は変更したとしても他のソースコードファイルには直接影響しませんし、影響の範囲を特定しやすく、気軽に変更できるというメリットがあります。
特に大規模なプログラムを大人数で開発を行うとソースコードの数も増えます。ソースコードファイルの数が増えると関数を呼び出すファイルも多くなる可能性もあり、一つ関数を修正するだけで多くのソースコードファイルに影響を及ぼすこともあります。
static を付けることでその影響範囲を局所化することができますので、static は特にソースコードファイルが多い開発時や複数人での開発時にメリットが大きいです。
まとめ
このページでは static 関数(静的関数)と static グローバル関数(静的グローバル変数)の使い方とメリットについて解説しました。
static は特に複数人で開発を行うときは意識して使用すると効果が高いです。
日頃から各関数やグローバル変数が他のファイルから使用されて良いかを意識し、積極的に static を使用していくと static をつける癖が付いて良いと思います。