【C言語】staticローカル変数の使い方・メリットを解説

staticローカル変数の解説ページアイキャッチ

このページでは static 変数、特に関数内で変数宣言される static ローカル変数について解説していきたいと思います。

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

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

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

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

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

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

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

static解説ページのアイキャッチstatic 関数と static グローバル変数の使い方・メリットを解説

ローカル変数という言葉を聞き慣れない方も多いかもしれませんが、要は「関数内で宣言する変数」のことを言っています。

主にグローバル変数と区別するためにこのような言い方をします。

ローカル変数は宣言した関数内でのみしか使用できませんが、グローバル変数はどの関数からも使用することができます。

このローカル変数とグローバル変数では static する効果が大きく異なるため、このページではこの2つを区別するために、この呼び方をさせていただきます(グローバル変数の static 化のメリットは上記ページで解説しています)。

グローバル変数の static 化は、確か他のソースコードに “非公開” にするためのものだよね

でもローカル変数は元々他のソースコードには非公開だし、そもそも他の関数に対しても非公開だよね…

なんで static つける意味あるんだろ…

公開範囲の観点だと、ローカル変数を static 化する意味は無いよ

だけど他の全く異なる効果がある!

その辺りを解説していくよ

static ローカル変数の宣言

static ローカル変数を宣言するためには、通常の変数宣言の先頭に static を付加するようにすれば良いです。

例えば int 型の変数 xstatic ローカル変数として宣言するのであれば、関数内に下記を記述します。

staticローカル変数の宣言
static int x;

もし static を付けなかった場合は、通常のローカル変数として宣言することになります。

では、ローカル変数と static ローカル変数ではどのような違いがあるのでしょうか?この点についてここから解説していきたいと思います。 

ローカル変数を static 化する効果 

ではローカル変数をわざわざ static ローカル変数として宣言することで、どのようにプログラムでの扱われ方が異なるのか?について解説していきたいと思います。

思い出した!

単なるローカル変数と static ローカル変数では、確か変数の生存期間が違うんだよね

その通り!

でもそれはなぜ?

理由は考えたことなかったなぁ…

おそらく多くの方が、ローカル変数と static ローカル変数では、下記のように生存期間が異なることをご存知なのでは無いかと思います。

  • ローカル変数:関数開始から終了まで
  • static ローカル変数:プログラム開始から終了まで

ではこれはなぜでしょう?

この理由が分かるように、もう一段掘り下げた解説をしていきたいと思います。

スポンサーリンク

メモリの配置先が変わる

まず前提として、変数は変数宣言時にメモリ上に配置されます。

変数がメモリ上に配置される様子

要は、「変数 = メモリの一部分」です。変数を利用してメモリに値を格納したりメモリから値を取得しているだけです。

で、ローカル変数と static ローカル変数ではこのメモリの配置先が下記のように異なります。

  • ローカル変数:スタック領域
  • static ローカル変数:静的領域

staticの有無によって異なる領域に変数が配置される様子

そして、この配置先が異なることを理由に、ローカル変数と static ローカル変数とではプログラムでの扱われ方が大きく異なります。

“静的領域” に配置される効果

では、変数が “静的領域” に配置された場合と “スタック領域” に配置された場合とでどのように変数の扱われ方が異なるのでしょうか?

この点について解説していきたいと思います。

静的領域に配置されるのでプログラム実行中はずっと生存

前述の通り、ローカル変数と static ローカル変数とでは下記のように生存期間が異なります。

  • ローカル変数:関数開始から終了まで
  • static ローカル変数:プログラム開始から終了まで

これは、ローカル変数が配置される “スタック領域” がいろんな変数で使い回しされるメモリであるのに対し、static ローカル変数が配置される “静的領域” が各変数専用のメモリであるためです。

“スタック領域” はいろんな変数で使い回しされるメモリ

ローカル変数は宣言時に「スタック領域」に配置されます。

スタック領域は、要はプログラム内でいろんな変数用に「使い回しされるメモリ領域」です。

MEMO

スタック領域は、その名の通りスタックのように扱われるメモリ領域です

ですが、今回は単なるローカル変数と 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ローカル変数が専用のメモリに配置される様子

つまり、静的領域のメモリは他の変数に使い回しされるようなことはありません。なので、静的領域に配置される static ローカル変数の生存期間は「プログラム開始からプログラム終了まで」となります。

例えばローカル変数の配置先は “スタック領域”で紹介したプログラムにおいて、関数 add で宣言する変数 cstatic ローカル変数にしてみましょう。

専用メモリへの配置を確認
#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 ローカル変数の配置先が異なることが理由になります。

スタック領域のサイズ < 静的領域のサイズ

前述の通り、ローカル変数は宣言すると “スタック領域” に、static ローカル変数は “静的領域” に配置されます。

ここで重要になるのが、各領域のサイズです。配置される変数のサイズがこの領域のサイズを超えてしまうと実行時にエラーになります(スタックオーバーフロー・セグメンテーションフォールトなど)。

また、基本的にスタック領域のサイズよりも静的領域として使用できるサイズの方が大きいです。

MEMO

スタック領域のサイズは、特に設定変更していなければ数 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 ローカル変数を使い分けしたプログラミングを行なっていただければ幸いです。

コメントを残す

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