static 関数と static グローバル変数の使い方・メリットを解説

static解説ページのアイキャッチ

このページでは C 言語における static 関数(静的関数)と static グローバル変数(静的グローバル変数)の使い方、これらを使用するメリットについて解説します。

まず static には下記の2つの役割があります。

  • 変数を静的領域に配置する
  • 変数や関数の公開範囲を制限する

前者はローカル変数に対して static を付加した時の static の役割になります。

一方で後者は、グローバル変数や関数に対して static を付加した時の static の役割になります。

これらの役割は全く異なるものになります。

本ページでは、後者の方、すなわち、グローバル変数や関数に対して static を付加した static グローバル変数とグローバル関数について解説していきます。

static ローカル変数については下記ページで解説していますので、こちらの詳細を知りたい方は下記ページを参照していただければと思います。

staticローカル変数の解説ページアイキャッチ【C言語】staticローカル変数の使い方・メリットを解説

一応 static の使い方は習ったんだけど使ったことないなぁ

メリットがよく分からない…

C 言語を始めたての方には特にそういう人は多いと思うよ

static は複数人でプログラム開発してるときに特にメリットの大きい修飾子なんだ

確かに今は独学で勉強してるからなぁ…

static は特に会社で大人数で開発するときなんかは必須になるよ

なるほど!

いつか役に立つ知識になるから、今は使わなくてもメリットだけでも覚えておくいいよ!

まずは使い方を軽く説明して、その次にメリットを詳しく説明するよ!

static 関数

まずは static 関数の使い方と、関数を static にする効果について解説します。

static 関数の使い方

では使い方について簡単に解説していきます。

まずは通常の(static でない)関数のおさらいです。通常の関数は下記のように定義を行います。

関数の定義
返却値の型 関数名(引数1の型 引数1の変数名, 引数2の型 引数2の変数名) {
    関数の処理
}

例えば、int 型の変数2つを引数とし、その2つを足し合わせた結果を int 型で返却する add 関数は下記のように定義できます。

関数定義の例
int add(int x, int y) {
    return x + y;
}

これに対し、static 関数では関数定義の先頭(つまり 返却値の型の前)に “static” という修飾子を追加で記述します。

static関数の定義
static 返却値の型 関数名(引数1の型 引数1の変数名, 引数2の型 引数2の変数名) {
    関数の処理
}

上記の add 関数を static 関数として扱うのであれば、前述の通り関数定義の先頭に static を記述すれば良いです。

static関数定義の例
static int add(int x, int y) {
    return x + y;
}

static を付けただけですが、これだけで関数の扱われ方が大きく変わります。どのように変わるかを続いて解説していきます。

スポンサーリンク

関数を static にする効果

続いては関数を static にする効果について解説していきます。

通常の関数(static でない関数)は、その関数の定義を行ったソースコードファイル内だけでなく、他のソースコードファイルからも参照できる状態にあります。

つまり、通常の関数は他のソースコードファイルからも呼び出し可能です。

このような関数は “公開” 関数などと呼ばれます。

公開関数のイメージ図

一方で、static 関数は、その関数定義を行ったソースコードファイル内からしか参照できません。つまり static 関数は他のソースコードファイルからは呼び出しすることが不可能です。

このような関数は “非公開” 関数と呼ばれます。

非公開関数のイメージ図

つまり、関数を static にする効果は他のソースコードファイルからの関数呼び出しを制限することと言えます。

これによってどのようなメリットがあるかについては、後述のstatic 関数と static グローバル変数のメリットで解説していきます。

static グローバル変数

次は static グローバル関数の使い方と、グローバル変数を static にする効果について解説します。

static グローバル変数の使い方

グローバル変数自体は、関数等のブロックの外で変数宣言することで作成することが可能です。ファイルの先頭付近で宣言するのが一般的ですね。

グローバル変数の宣言
変数の型 変数名;

int main(void){
    ・・・・
}

例えば int 型の x という変数をグローバル変数として扱うのであれば、下記のように記述すれば良いです。

グローバル変数の宣言例
int x;

int main(void){
    ・・・・
}

ご存知の通り、グローバル変数として変数宣言することで、ファイル内のどこからでもこの変数を参照することが可能になります。便利です。

static グローバル変数についてもグローバル変数と宣言する場所は同じです。関数外で変数宣言を行います。

ただし、static グローバル変数の場合は変数の型の前に “static” という修飾子を追加で記述します。

staticグローバル変数
static 変数の型 変数名;

int main(void){
    ・・・・
}

例えば int 型の x という変数を static グローバル変数として扱うのであれば、下記のように記述すれば良いです。

staticグローバル変数の宣言例
static int x;

int main(void){
    ・・・・
}

スポンサーリンク

グローバル変数を static にする効果

続いてはグローバル変数を static にする効果の説明です。この効果は前述の関数を static にする効果とほぼ同様です。

通常のグローバル変数は、そのグローバル変数の宣言を行ったソースコードファイル内だけでなく、実は他のソースコードファイルからも参照できる状態にあります。

つまり、通常のグローバル変数は他のソースコードファイルからも使用可能です。

公開グローバル変数のイメージ図

MEMO

図中の extern は他のファイルで定義・宣言されているグローバル変数や公開関数を使用するための宣言になります

例えば下記では、他のソースコードで宣言されている変数 val を自身のソースコードで使用するための宣言になります。

extern int val;

普通は他のファイルで使用しても良いグローバル変数や公開関数は「明示的にヘッダーで」宣言されますが、それが無くても extern を使えば他のファイルから使用できてしまいます

あまり使用しませんが、ここでは便宜上、このような変数を公開グローバル変数と呼ばせていただきます。

一方で、static グローバル変数は、その変数宣言を行ったソースコードファイル内からしか参照できません

つまり static グローバル変数は他のソースコードファイルからは使用することが不可能です。

非公開グローバル変数のイメージ図

こちらは先程と同様に、便宜上、非公開グローバル変数と呼びます。

つまり、グローバル変数を static にする効果は「他のソースコードファイルからの使用を制限すること」と言えます。

他にも関数内でのみ使用可能なローカル変数というものも存在します。ややこしいのでそれぞれの変数の有効範囲(スコープ)を下の表にまとめておきます。

変数の種類有効範囲
ローカル変数ブロック内
静的グローバル変数ファイル内
グローバル変数ファイル内
(リンクする)他のファイル

static 関数と static グローバル変数のメリット

特にこの2つが活躍するのが、複数人で同時に複数のソースコードファイルを編集しながら開発するときです。

前述の通り、関数やグローバル変数を static 化することで、その関数やグローバル変数が使用可能な範囲を、定義・宣言したファイル内に制限することができます。

これにより、下記のメリットがあります。

  • 複数ファイル間での名前の衝突が防げる
  • 変更時の影響範囲を局所化できる

それぞれについて実例を用いて説明していきます。

複数ファイル間での名前の衝突が防げる

C言語においては、リンクし合うソースコード(正確に言うとリンクし合うオブジェクト)内で、同じ名前の公開関数や公開グローバル変数を使用できないというルールがあります。

同じ名前の関数があると、どの関数が呼び出されているか分かりませんもんね。変数も同様です。

リンクし合うソースコードに同じ名前の公開関数や公開グローバル変数が存在する場合、名前の衝突が起きてリンク時にエラーになります。

それを確認するために、下記の main.csub.c をコンパイル&リンクしてみましょう!

main.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;
}
sub.c
#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 を定義・宣言している」点です。

エラーを紹介することが目的なので、特にプログラム自体に意味はないです…。

では実際に、コマンドラインから下記コマンドでコンパイル・リンクしてみます。

$ 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)

このエラーがまさに、ここで説明した名前の衝突(duplicate symbol )です。

つまり、公開関数や公開グローバル変数は、他のソースコードと名前が被るとリンクできなくなってしまいます。

ですので、気軽にありきたりな名前をつけてしまうと、開発チームの他の方が作成した関数やグローバル変数と名前が被ってリンクが通らなくなる可能性があります。そして、これにより開発チームに迷惑をかけてしまう可能性もあります。

名前衝突でリンクに失敗した時の状況

このような名前の衝突を防ぐためには、他の人と被らないような関数名や変数名を考える必要があります。

ただし、static を使えばこれだけで上記の問題は解決します。これは static 化した関数は “非公開” 関数に、グローバル変数は “非公開” グローバル変数となるためです。

名前が被ってリンクエラーになるのはあくまでも “公開” 関数や “公開” グローバル変数同士の場合だけです。ですので、これらを static 化して “非公開” にすることでリンクエラーを解消することができます。

例えば上記の main.c を下記のように変更してみましょう。

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.csub.c について考えてみましよう。

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;
}
sub.c
#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 を下記のように変更してみましよう。

変更後のsub.c
#include <stdio.h>
  
/* 関数のプロトタイプ宣言 */
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 に食い違いがあるためです。

MEMO

上記のような計算結果になるのは、main.c から calc 呼び出し時に2つ目の引数を指定できていないので、sub.c の関数 calc では2つ目の引数を初期値(つまり不定値)のまま使用することになっているためだと思います

このように、関数の変更は「その関数の呼び出し元」に影響を及ぼします。おそらく返却値の型や引数が変わると、コンパイラによってはリンク時にエラーが出ると思います。

またエラーが発生しなかったとしても、関数に必要な引数が渡せなくなったりしますので、思った通りの計算結果が得られなくなってしまいます。

つまり、公開関数はうまく変更してやらないと、関数呼び出し側が期待した動きでなくなり、プログラム全体として上手く動作しなくなる可能性があります。そうなると一緒に開発している人に迷惑をかける可能性もあります。

公開関数を勝手に変更した時の状況

特に公開関数の場合、いろんなファイルから呼び出されている可能性があるので、いろんなファイルに影響を及ぼす可能性があります。

公開関数変更時の影響範囲

しかし、static 関数の場合は非公開ですので、他のファイルから使用されることはありません。

ですので、static 関数を変更したとしても直接影響を及ぼすのはその関数が定義されているファイル内のみです。

非公開関数変更時の影響範囲

つまり、static を付加することで、その関数の変更による影響範囲をその関数を定義するファイル内に局所化することができます。

なので、static 関数の場合は変更したとしても他のソースコードファイルには直接影響しませんし、影響の範囲を特定しやすくなるというメリットがあります。

特に大規模なプログラムを大人数で開発を行うとソースコードの数も増えます。ソースコードファイルの数が増えると関数を呼び出すファイルも多くなる可能性もあり、一つ関数を修正するだけで多くのソースコードファイルに影響を及ぼすこともあります。

static を付けることでその影響範囲を局所化することができますので、static は特にソースコードファイルが多い場合や複数人での開発時にメリットが大きいです。

まとめ

このページでは static 関数(静的関数)と static グローバル関数(静的グローバル変数)の使い方とメリットについて解説しました。

static は特に複数人で開発を行うときは意識して使用すると効果が高いです。

独学でC言語を学んでいたり、一人で開発を行なっているような場合でも、日頃から各関数やグローバル変数が他のファイルから使用されて良いかを意識し、積極的に static を使用していくと static をつける癖が付いて良いと思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です