static指定子は、ローカル変数、グローバル変数の他、関数にも付加することがあります。効果もそれぞれ異なります。本投稿では、static指定子の働きについてまとめます。
実行環境は下表の通りです。
開発環境 | Microsoft Visual Studio Community 2019 Version 16.11.7 |
OS | Windows 10 バージョン21H1 |
仕様
static指定子の仕様を下表にまとめます。
付加対象 | 動作 |
ローカル変数 | ローカル変数をstatic指定を行って定義します。すると、関数から抜け出し、再度、当該関数へアクセスしてきた際、static指定で定義した変数は、格納しているデータを保持しています。 |
グローバル変数 | グローバル変数をstatic指定を行って定義します。すると、他のソースファイルから参照や代入ができなくなります。 |
関数 | static指定された関数は、他のソースファイルの関数から呼び出すことができなくなります。同一ソースファイル内の関数からのみ、呼び出し可能です。 |
では、具体例で見ていきます。
ローカル変数へのstatic付加
ローカル変数へstatic指定子を付加する場合のサンプルです。
初期値の保持
#include <stdio.h>
// staticをローカル変数に付加する例
int calc(void)
{
static int c = 5; // 定数として5を記憶
return c++; // returnしてから、インクリメント
}
void main(void)
{
printf("c = %d\n", calc()); // calc実行(1回目)
printf("c = %d\n", calc()); // calc実行(2回目)
printf("c = %d\n", calc()); // calc実行(3回目)
}
実行結果は次の通りです。初回のcalc()呼び出し時に5で初期化された後、インクリメントします。2回目、3回目の呼び出しでもインクリメントした値が残っているのが確認できます。
初期化を漏らしたケース
static指定子を使用すると、前回の関数呼び出し時の処理結果を記憶しますが、以下のサンプルのように、変数宣言時の初期化を行わなければ、ただのローカル変数となってしまいます。
#include <stdio.h>
// staticをローカル変数に付加する例
int calc(void)
{
static int c; // 初期化しない
c = 5;
return c++;
}
void main(void)
{
printf("c = %d\n", calc());
printf("c = %d\n", calc());
printf("c = %d\n", calc());
}
実行結果は次の通りです。初回のcalc()の実行結果は5となりますが、2回目、3回目も同じ5となります。初期化を漏らすとバグの元です。
グローバル変数へのstatic付加
グローバル変数へstatic指定子を付加する場合のサンプルです。
同一ソースファイル内でのアクセス
#include <stdio.h>
// 別ソースファイルの関数に対するプロトタイプ宣言
void Testfunc(void);
// グローバル変数(実体)の宣言
static int gCount1; // グローバル変数(static)
int gCount2; // グローバル変数
// staticをローカル変数に付加する例
int calc(void)
{
static int c; // 初期化しない
c = 5;
return c++;
}
void main(void)
{
gCount1 = 9;
gCount2 = 99;
printf("gCount1=%d\n", gCount1);
printf("gCount2=%d\n", gCount2);
Testfunc();
}
#include <stdio.h>
// グローバル変数
extern int gCount1;
extern int gCount2;
void Testfunc(void)
{
printf("gCount1=%d\n", gCount1);
printf("gCount2=%d\n", gCount2);
return ;
}
Static_Sample.cの中で宣言したグローバル変数gCount1,gCount2ですが、gCount1にのみ、static指定子を付加しています。つまり、gCount1は宣言したソースファイルStatic_Sample.c内に定義された関数からのみアクセスできるということです。
この状態でビルドを行うと、TestFunc.cからgCount1を参照していることに対して、エラーとします。
TestFunc.obj : error LNK2001: 外部シンボル _gCount1 は未解決です
関数へのstatic付加
関数へstatic指定子を付加する場合のサンプルです。
同一ソースファイル内でのみ使用する関数
#include <stdio.h>
// 別ソースファイルの関数に対するプロトタイプ宣言
void Testfunc(void);
void main(void)
{
Testfunc();
}
#include <stdio.h>
// 関数定義の先頭にstaticを付加
static void Testfunc(void)
{
printf("Testfunc CALL.\n");
return ;
}
この状態でビルドを行うと、Static_Sample.cの中に定義する関数からTestFunc.c内のTestfunc()を呼び出しできないことから、エラーとなります。
Static_Sample.obj : error LNK2019: 未解決の外部シンボル _Testfunc が関数 _main で参照されました
どんな時に使える?
C言語において、ソースファイル毎に同一名称の関数を作成したい時は、以下のようにします。
#include <stdio.h>
// 別ソースファイルの関数に対するプロトタイプ宣言
void Testfunc(void);
void Testfunc_Wrapper(void);
static void Testfunc(void)
{
printf("Testfunc2 CALL.\n");
return;
}
void main(void)
{
Testfunc_Wrapper(); // 別ソースで定義
Testfunc();
}
#include <stdio.h>
// Testfunc()はstatic指定のため、他ソースファイルから呼び出し禁止
static void Testfunc(void)
{
printf("Testfunc1 CALL.\n");
return;
}
// Testfunc()をラップした関数だが、static指定ではないため、他ソースファイルから呼び出し可能
void Testfunc_Wrapper(void)
{
Testfunc();
}
実行結果は以下の通りです。隠蔽的なことができるようです。『Testfunc1 CALL』は本来、static指定子が付加されたTestfunc()なので、呼び出せませんが、ラッパー関数を作ることで呼び出せています。
まとめ
static指定子のうち、グローバル変数と関数に付加するケースでは、不正なコードはビルド時にエラーとなるので問題ありません。ローカル変数にstaticを付加する場合は、初期化を漏らしてもビルドエラーにはならず、不具合となる事例がよく見られます。注意しましょう。
コメント