【C言語入門】staticの使い方まとめ(関数、変数、定数、構造体)

staticって使ってますか?staticをよく使うケースは、char型配列(文字列)を戻り値とする関数内でchar型配列を定義する場合です。そのほかにも、別のファイルから使用できないように変数や関数を修飾する場合にも使用します。

staticの使い方は主にこの2種類のになりますが、それぞれの使い方を混同してしまうこともあります。この記事では、staticについて

  • staticとは
  • staticの使い方

という基本的な内容から構造体内でのstaticの使い方についてなど応用的な内容についても解説していきます。今回はstaticについて、使い方をわかりやすく解説します!

目次

staticとは

staticとは、静的なという意味です。static修飾子を用いると静的記憶クラスを使用し、静的に存在する領域にデータを保持することができます。

例えば、char型配列(文字列)にstatic修飾子を付け定義すると、その文字列のメモリを確保し値を保持し続けます。配列のメモリを保持する以外にも、関数内のローカル変数にstatic修飾子を付けると変数の値を保持し続けることができます。

これに対してstatic修飾子を付けない一般的な変数は自動変数とよびます。自動変数は関数内の処理が終了すると変数は消去され、関数が呼び出される度に自動変数は初期化されます。

自動変数は変数の前に「auto」を付けますが、一般には明示しなくても省略して使用しています。staticの使い方はこの他にも、変数や関数を別のファイルから使用するのを防ぐために使う場合もあります。

staticの使い方について

staticは2種類の使い方に分かれます。メモリを確保して保持し続ける使い方と別のファイルから使用するのを防ぐ使い方です。この2種類の使い方を変数、定数、関数に対して用いる場合についてサンプルコードで確認していきましょう。

変数について

static修飾子を変数に付ける場合について説明します。static修飾子はローカル変数に付ける場合とグローバル変数に付ける場合とで役割が異なってきます。ローカル変数とは関数内のみで使用する変数のことで、関数が呼び出される度に初期化される自動変数です。

ローカル変数にstatic修飾子を付けると変数は値を保持し続け、関数を呼び出しても初期化されることはありません。グローバル変数とは関数ブロック外に記述し、記述されたファイル内のどの関数で使用できる変数のことです。

グローバル変数にstatic修飾子を付けると、記述されたファイル内のみでの使用に制限されます。宣言、定義はソースファイルで行います。ヘッダーファイルでstatic修飾子を付けて宣言することはできません。

ちなみに、static修飾子ではなくextern修飾子を付けると他のファイルからも使用できます。それではサンプルコードで確認していきましょう。

なおサンプルコードは、staticグローバル変数を定義しているソースファイル「test.c」と別のファイルからアクセス可能なグローバル変数を宣言しているヘッダーファイル「test.h」と実行ファイル「main.c」ファイルから構成されています。

test.h:

// 別のファイルからアクセス可能なグローバル変数を宣言
extern char greeting[];
extern float pi;
 
/*  error: static declaration of 'new_pi' follows non-static declaration
float new_pi;
*/
 

test.c:

#include <stdio.h>
#include "test.h"
 
static char pref[] = "Tokyo"; // staticグローバル変数(配列)
static float new_pi = 3.0f; // staticグローバル変数
 
char greeting[] = "Hello"; // グローバル変数
float pi = 3.14f; // グローバル変数
 
void exec() {
	printf("static: %f, %s\n", new_pi, pref);
}
 

main.c:

#include <stdio.h>
#include "test.h"
 
char* str() {
	static char greeting[64]; // 静的なメモリの確保
	sprintf(greeting, "%s %s!", "Hello", "World");
	return greeting;
}
 
int counter1() {
	static int cnt = 0; // staticローカル変数
	cnt++;
	return cnt;
}
 
int counter2(){
	int cnt = 0; // ローカル変数
	cnt++;
	return cnt;
}
 
int main(void) {
	//静的なメモリの値を取得
	printf("%s\n", str());
 
	int cnt1 = 0, cnt2 = 0;
	for(int i = 0; i < 3; i++) {
		cnt1 = counter1(); // staticローカル変数を取得
		cnt2 = counter2(); // ローカル変数を取得
		printf("%d回目, 初期値:%d\n", cnt1, cnt2);
	}
 
	//別のファイルのグローバル変数を出力表示
	printf("%s\n", greeting);
	printf("%f\n", pi);
 
	return 0;
}
 

実行結果:

Hello World!
1回目, 初期値:1
2回目, 初期値:1
3回目, 初期値:1
Hello
3.140000

このサンプルコードではソースファイル「test.c」で文字列型のstaticグローバル変数「pref」とfloat型のstaticグローバル変数「new_pi」を定義しています。

これに対して文字列型のグローバル変数「greeting」とfloat型のグローバル変数「pi」を定義し、ヘッダーファイル「test.h」でextern修飾子を付けて宣言しています。

extern修飾子を付けて宣言していますので、実行ファイル「main.c」でインクルードして使用することできています。staticグローバル変数「pref」や「new_pi」は「greeting」とは異なり、別のファイルでは使用することができません。

「greeting」や「new_pi」を別のファイルに記述するとコンパイルエラーが発生します。このサンプルコードでは実行ファイル「main.c」の定数文字列「greeting」はstaticを使って静的メモリを確保しています。

また、実行結果を確認するとstaticローカル変数を取得した変数「cnt1」とローカル変数を取得した「cnt2」で表示が異なります。staticローカル変数は値を保持し続け呼び出されても初期化されないので、counter1関数内で1ずつ値が増えています。

これに対してstatic修飾子のないローカル変数は呼び出される度に値が初期化され、counter2関数内で1加算された値が出力されています。このようにstatic修飾子が付くか付かないかで出力される値が異なりますので、使い分けには注意しましょう!

定数について

変数の前にconstを付けて読取専用の定数にstatic修飾子を付けた場合も、記述したひとつのファイル内のみに使用を制限することができます。それではサンプルコードで確認していきましょう。

test.h:

// 別のファイルからアクセス可能なグローバル変数(読取専用)を宣言
extern const char greeting[];
extern const float pi;
 
/*  error: static declaration of 'new_pi' follows non-static declaration
const float new_pi;
*/
 

test.c:

#include <stdio.h>
#include "test.h"
 
static const char pref[] = "Tokyo"; // staticグローバル変数(配列:読取専用)
static const float new_pi = 3.0f; // staticグローバル変数(読取専用)
 
char const greeting[] = "Hello"; // グローバル変数
float const pi = 3.14f; // グローバル変数
 
void exec() {
	printf("static: %f, %s\n", new_pi, pref);
}
 

main.c:

#include <stdio.h>
#include "test.h"
 
char* str() {
	static const char sayHello[64] = "Hello"; // 静的なメモリの確保(読取専用)
	static char greeting[64] = ""; // 静的なメモリの確保
	sprintf(greeting, "%s %s!", sayHello, "World");
	return greeting;
}
 
int main(void) {
	//静的なメモリの値を取得
	printf("%s\n", str());
 
	//別のファイルのグローバル変数を出力表示
	printf("%s\n", greeting);
	printf("%f\n", pi);
 
	return 0;
}
 

実行結果:

Hello World!
Hello
3.140000

このサンプルコードではソースファイル「test.c」でstaticな定数「new_pi」を定義しています。これをヘッダーファイル「test.h」で宣言しようとするとひとつのファイル内のみに使用が制限されるので、コンパイルエラーが発生しています。

なお、別のファイルで使用する場合はヘッダーファイル「test.h」で「greeting」や「pi」のように、extern修飾子を付けて使用します。また実行ファイル「main.c」のstr関数では定数「sayHello」を定義しています。

定数文字列「sayHello」はstaticを使って静的メモリを確保し、constを使って読取専用となっています。

関数について

関数にstatic修飾子を付けた場合も、記述したひとつのファイル内のみに使用を制限することができます。それではサンプルコードで確認していきましょう。

test.h:

extern char *country;
extern void greeting();
 
/* note: previous declaration of 'text' was here
 * static関数をヘッダーファイルで宣言すると
 * ソースファイルで定義する際にコンパイルエラーが発生
char* text();
*/
 

test.c:

#include <stdio.h>
#include "test.h"
 
static const char sayHello[32] = "Hello"; // 他のファイルから使用不可
char *country = "World"; // 他のファイルから変更可能
 
static char* text() {
	static char txt[32]; // 静的なメモリの確保
	sprintf(txt,"%s %s!", sayHello, country); // 文字列の結合
	return txt;
}
 
void greeting() {
	printf("%s\n", text());
}
 

main.c:

#include <stdio.h>
#include "test.h"
 
int main(void) {
	country = "Japan";
	greeting();
 
	return 0;
}
 

実行結果:

Hello Japan!

このサンプルコードではソースファイル「test.c」でstaticな関数「text」を定義しています。これをヘッダーファイル「test.h」で宣言しようとするとひとつのファイル内のみに使用が制限されるので、コンパイルエラーが発生しています。

なお、別のファイルで使用する場合はヘッダーファイル「test.h」で「greeting」関数のように、extern修飾子を付けて使用します。

構造体でのstaticの使い方

C言語の構造体でも変数を持つことは可能です。構造体内の変数に対してstatic修飾子を付けることはできませんが、構造体型を宣言した実体にはstatic修飾子を付けることができます。構造体型を宣言した実体にstatic修飾子を付けると関数内のローカル変数のように値を保持し続けることができます。

それではサンプルコードで確認していきましょう。

test.h:

typedef struct num2{
	int num;
} number2;
 
extern number2 num2;
 
/* warning: 'num3' defined but not used [-Wunused-variable]
 * 警告は出るが、コンパイル&実行可能で別のファイルから使用可能
 * 一つのファイルのみで使用する場合はヘッダーファイルで宣言しないよう注意!!
typedef struct num3{
	int num;
} number3;
 
static number3 num3;
*/
 

test.c:

#include <stdio.h>
#include "test.h"
 
typedef struct num4{
	int num;
} number4;
 
number4 num4;
 

main.c:

#include <stdio.h>
#include "test.h"
 
typedef struct num{
	int num;
	//static num; // コンパイルエラー
} number;
 
int main(void) {
	for (int i = 0; i < 3; i++) {
		static number s_num;
		s_num.num++;
		number num;
		num.num = 0;
		num.num++;
		printf("s_num1.num : %d , num1.num : %d\n", s_num.num, num.num);
	}
 
	for (int i = 0; i < 3; i++) {
		static number2 s_num2;
		s_num2.num++;
		number2 num2;
		num2.num = 0;
		num2.num++;
		printf("s_num2.num : %d , num2.num : %d\n", s_num2.num, num2.num);
	}
	return 0;
}
 

実行結果:

s_num1.num : 1 , num1.num : 1
s_num1.num : 2 , num1.num : 1
s_num1.num : 3 , num1.num : 1
s_num2.num : 1 , num2.num : 1
s_num2.num : 2 , num2.num : 1
s_num2.num : 3 , num2.num : 1

このサンプルコードではヘッダーファイル「test.h」でstaticな構造体型「number3」を宣言しています。staticを付けていますがコンパイルで警告が出るだけで、externを付けた場合と同様に実行することが可能です。

ですので構造体をひとつのファイル内のみで使用するように制限する場合は、ヘッダーファイルには宣言せずに構造体型「number4」のようにソースコードで宣言、定義するようにしましょう。

また実行ファイル「main.c」の構造体型「number」のように構造体内の変数にstatic修飾子を付けることはできません。構造体型を宣言した実体「s_num」や「s_num2」にstatic修飾子を付けると値を保持し続けることができています。

まとめ

ここでは、staticの使い方について説明しました。ローカル変数に対して使用すると値を保持し続けることができます。

またグローバル変数や定数、関数に対して使用するとひとつのファイル内で使用を制限し、他のファイルから変更されないように隠すことができます。使いこなすことができるように、この記事を何度も参考にして下さいね!

この記事を書いた人

熊本在住のフリープログラマ兼ライターです。C/C++/C#、Java、Python、HTML/CSS、PHPを使ってプログラミングをしています。専門は画像処理で最近は機械学習、ディープラーニングにはまっています。幅広くやってきた経験を活かしてポイントをわかりやすくお伝えしようと思います。
お問合せはこちらでも受け付けています。
info@sss-lab.com

目次