【C言語】ストレージクラスとは?auto・register・static・externの違いを徹底解説
はじめに
これまでの章では、変数とデータ型について解説してきました。本章では、変数を「スコープ(有効範囲)」と「可視性」という観点から分類するストレージクラスについて詳しく見ていきましょう。
スコープとは
スコープとは、一般的に変数の寿命(ライフタイム)を表す用語です。変数がどれくらいの期間メモリ上に存在し続け、いつ破棄されるのかを示します。
可視性とは
可視性は、その変数がどこから参照(アクセス)できるかを示します。たとえばローカル変数は、他の関数やファイルから使用することができず、宣言されたブロックの中でしか見えません。
ブロックとは
ブロックとは、2つの中括弧 {…} で囲まれた一連のコード行として定義されます。以下がブロックの一例です。
{
//line1
//line2
//line3
}上記がひとつのブロックです。
ストレージクラスごとのスコープと可視性の一覧
| ストレージクラス | 宣言方法 | スコープ | 可視性 |
|---|---|---|---|
| auto | グローバル | — | — |
| auto | ローカル | ブロック | ブロック |
| register | グローバル | — | — |
| register | ローカル | ブロック | ブロック |
| static | グローバル | プログラム全体 | ファイル内 |
| static | ローカル | プログラム全体 | ブロック内 |
| extern | グローバル | プログラム全体 | プログラム全体 |
| extern | ローカル | プログラム全体 | ブロック内 |
ストレージクラスの基本構文
<ストレージクラス> <データ型> 変数名; 例:static int my_var = 0;
注意:ストレージクラスを指定せずに「<データ型> 変数名;」と記述した場合、自動的に「auto」ストレージクラスとして扱われます。
auto(自動変数)
autoは、すべてのローカル変数に対するデフォルトのストレージクラスです。
{
int mount;
auto int month;
}上の例では、同じストレージクラスに属する2つの変数を定義しています。「auto」は関数内、つまりローカル変数に対してのみ使用できます。
register(レジスタ変数)
registerストレージクラスは、RAMではなくCPUのレジスタに格納すべきローカル変数を定義するために使われます。そのため、変数の最大サイズはレジスタサイズ(通常は1ワード)と等しくなり、メモリ上の番地を持たないため、単項演算子「&」(アドレス演算子)を適用することはできません。
{
register int miles;
}registerは、カウンタのように高速なアクセスが求められる変数に対してのみ使うべきです。また、「register」と宣言したからといって、必ずしもその変数がレジスタに格納されるとは限らない点にも注意してください。ハードウェアや実装上の制約に応じて、レジスタに格納される「可能性がある」という程度の意味にとどまります。
static(静的変数)
staticストレージクラスは、ローカル変数に対して、スコープに入るたびに生成し抜けるときに破棄するのではなく、プログラムの生存期間中ずっと存在させ続けるようコンパイラに指示します。そのため、ローカル変数をstaticにすることで、関数呼び出しをまたいで値を保持できるようになります。
static修飾子はグローバル変数にも適用できます。この場合、その変数のスコープは宣言されたファイル内に限定されます。
なお、C++においてstaticをクラスのメンバ変数に適用した場合は、そのクラスのすべてのオブジェクトで共有される単一のコピーが作られます。
サンプルコード
#include <stdio.h>
/* 関数のプロトタイプ宣言 */
void func(void);
static int count = 5; /* グローバル変数 */
int main(void) {
while(count--) {
func();
}
return 0;
}
/* 関数の定義 */
void func(void) {
static int i = 5; /* ローカルstatic変数 */
i++;
printf("i is %d and count is %d\n", i, count);
}実行結果
i is 6 and count is 4 i is 7 and count is 3 i is 8 and count is 2 i is 9 and count is 1 i is 10 and count is 0
このように、ローカルstatic変数「i」の値は関数呼び出しの間も保持され、毎回増加していっているのがわかります。
extern(外部変数)
externストレージクラスは、すべてのプログラムファイルから参照可能なグローバル変数への参照を提供するために使われます。externを使用する場合、その変数を初期化することはできません。代わりに、変数名が以前に定義済みの格納場所を指すことになります。
複数のファイルで構成されるプログラムにおいて、あるファイルで定義したグローバル変数や関数を他のファイルでも利用したい場合には、利用する側のファイルでexternを使って参照を宣言します。簡単にまとめると、externは「別のファイルで定義されたグローバル変数や関数を宣言する」ために使われるものです。
extern修飾子は、2つ以上のファイルが同じグローバル変数や関数を共有するケースでよく使われます。以下に具体例を示します。
サンプルコード(main.c)
#include <stdio.h>
int count;
extern void write_extern();
int main(void) {
count = 5;
write_extern();
return 0;
}サンプルコード(support.c)
#include <stdio.h>
extern int count;
void write_extern(void) {
printf("count is %d\n", count);
}実行結果
count is 5
main.cで定義したグローバル変数「count」の値が、extern宣言を通じてsupport.c側の関数から正しく参照できていることが確認できます。
-
Pythonの多重継承クラスにおける属性検索とMROの仕組みを徹底解説
Pythonにおける多重継承とはPythonでは、1つのクラスを複数の基底クラス(親クラス)から派生させることができます。これを多重継承(Multiple Inheritance)と呼びます。多重継承を使用すると、すべての基底クラスの機能(属性やメソッド)が派生クラスに引き継がれます。構文は単一継承とほぼ同じで、括弧内に基底クラスをカンマ区切りで並べるだけです。多重継承の基本構文class Super1: pass class Super2: pass class MultiDerived(Super1, Super2): passこの例では、MultiDerive
-
なぜクラスを作るのか?抽象化の重要性と良いクラス設計の基本
前回の記事「なぜnilを使うのか」に続き、普段当たり前と思って使っているものについても、同じ問いを立ててみるのは良いアイデアではないでしょうか。 例えば… 巨大で醜いコードの塊を1つ作るのではなく、なぜ私たちはクラスを使うのでしょうか? 基本的なレベルで言えば、コードとデータを論理的な単位に整理するためにクラスを使います。しかし、それだけではありません。 クラスを使うと、抽象化(abstraction)を作り出せるのです。 抽象化とは何か? 実は、私たちは毎日、毎分、抽象化を利用しています。 今この記事を書いているキーボードは、次のようなもので構成されています。 ケーブル Cherry MX