こんにちは、ナナです。
メモリの種類、3つ目の「静的メモリ」について紹介します。
「静的メモリ」とはアプリケーション内で常駐するメモリのことです。スタックメモリとの違いを意識して使い分ける必要があります。
本記事では次の疑問点を解消する内容となっています。
では、静的メモリの扱いを学んでいきましょう。
静的メモリの特徴を知ろう
今日のテーマは「静的メモリ」とやらね。「静的」ってなんですの?
「静的」は「あらかじめ決められた」というような意味だよ。ちょっとわかりづらいかもしれないけど、静的メモリは事前に使う用途が決められたメモリ領域のことだよ。
静的メモリは、他のメモリと比べて次の特徴を持ちます。
種類 | 配置データ | Read/Write | 容量 | 説明 |
プログラムメモリ | 関数・定数 | R | 大サイズ | 関数や定数が配置 |
スタックメモリ | 変数 | R/W | 小サイズ | 関数内で定義した変数が配置 |
静的メモリ | 変数 | R/W | 中サイズ | 関数外で定義した変数が配置 |
動的メモリ | 変数 | R/W | 大サイズ | メモリ確保関数で取得 |
静的メモリはグローバル変数が配置されるメモリ
静的メモリは、グローバル変数が配置されるメモリです。
特徴はプログラムが動作してから終了するまで常に変数のラベルが貼られており、メモリ上に変数が常駐することです。
スタックメモリに配置されるローカル変数と異なり、プログラム動作中にラベルが破棄されることがないため、特定の関数呼び出しに依存せず値を保持し続けることができます。
全体制御とグローバル変数定義
#include <stdio.h>
// グローバル変数定義
short gCount = 0;
int main(void)
{
disp();
count();
disp();
return 0;
}
カウントアップと表示処理
#include <stdio.h>
extern short gCount;
void count(void)
{ // カウントアップ
gCount++;
}
void disp(void)
{ // グローバル変数の表示
printf("%d\n", gCount);
}
実行結果は次のものになります。
0
1
関数の外側で定義されたグローバル変数gCountは、count関数とdisp関数の両方から参照ができ、count関数によりインクリメントされますが、その結果は保持され続けます。
スタックメモリと違い、静的メモリに配置された変数はラベルが破棄されることがありません。
静的メモリの変数の初期値
C言語には「静的メモリは初期化せずとも初期値を0とする」というルールがあります。つまり、次のようにグローバル変数を定義しても初期値は0になります。
short gCount;
しかし、組み込み開発においてはこのルールが絶対に守られる保証はありません。
そもそも、グローバル変数の初期値を0にするという処理は、スタートアップルーチンと呼ばれるシステム起動を司る処理にて実施されます。
組み込み開発では起動速度を早くするために、意図的にグローバル変数の0初期化処理を実施しない場合があるのです。
そのため、組み込み開発ではグローバル変数が0で初期化されることを前提としないようにしなければなりません。定義と同時に適切な値で初期化するというのはその方法のひとつでしょう。
静的メモリとstatic修飾子の関係
あたくしが定義してきたグローバル変数は、「静的メモリ」とやらの場所に作られていたのね。存じあげませんでしたわ。あぁた、静的メモリに関してもっと知っておくべきことはないのかしら?
じゃあ、静的メモリと関連が深い「static修飾子」について解説しようね。
静的メモリと関連の深い「static修飾子」について解説しましょう。
static修飾子は皆さんのプログラムをより安全に管理するための機構です。staticをどのように使い分けるかを学びましょう。
static修飾子の付与方法
変数定義にstatic修飾子を与えることで、対象の変数はstatic変数になります。
書き方
static データ型 変数名;
使用例
static short gNumber;
static short gNumber = 100;
このようにstatic変数は簡単に定義することができます。通常の変数と同様に初期値を与えることも可能です。
staticが付かないグローバル変数の扱い
まずはグローバル変数にstatic修飾子を付けない場合に何ができるかを示しましょう。
C言語による開発は複数のソースファイルから構成されることも珍しくありません。
次のようにmain.c、sub.cの2つのファイルが存在したときグローバル変数gNumberはどちらのファイルからでも参照が可能です。
つまり、グローバル変数は他の関数どころか他のファイルからの参照も可能となります。
グローバル変数の外部参照宣言とは
「外部参照宣言」という言葉が出てきたのでついでに解説しておきましょう。
「外部参照宣言」とは他のファイルで定義されたグローバル変数を参照するための宣言です。
先例においてgNumberはmain.cで定義されているわけです。gNumberをsub.cから参照するためには外部参照宣言が必要となります。
書き方
extern データ型 変数名;
使用例
extern short gNumber;
extern short gNumber = 100; この書き方は×
extern使用時は初期値を与えることはできないことに注意が必要です。
なぜ、外部参照宣言は必要なのか
他のファイルで定義されたグローバル変数を参照する際に、外部参照宣言はなぜ必要なのでしょうか。この理由にはコンパイラが強く関わっているのです。
コンパイラの校正機能は、皆さんが作ったプログラムが正しいかをチェックする機能でした。
次のプログラムをコンパイルすると、どうなるか皆さんわかりますか?
エラー ’i’: 定義されていない識別子です。
はい、コンパイルエラーが発生しますね。ここまでプログラムを実際に書いてきた方であれば一度は経験しているでしょう。
では、次のプログラムをコンパイルするとどうなるでしょうか。
エラー ‘gNumber’: 定義されていない識別子です。
同じエラーが発生しますね。
でもですよ「ちょっと待て、gNumberはmain.cに定義されている変数だから、定義されていないという指摘はおかしいだろ」と思うわけです。
このように考えた方はコンパイラというものへの理解が足りていません。
コンパイラはソースファイル単位で校正機能を実施するのです。コンパイラにとってsub.cを校正する際にmain.cの存在など知らないのですから、このコンパイルエラーは正当な主張だと思いませんか。
ここで登場するのが「外部参照宣言」です。
外部参照宣言とはその名の通り「外部のファイルに変数定義があるから、このファイル内で参照していますよ」というコンパイラへ向けた皆さんからのメッセージなのです。
「外部参照宣言」はコンパイラに対して変数の参照を許可してもらうために必要なんです。
staticが付与されたグローバル変数
本筋から大分外れましたが、static修飾子の役割に話を戻しましょう。
グローバル変数にstatic修飾子を付与した場合にどのような効果があるのかを見てみましょう。
static修飾子が付与されたグローバル変数は、定義ファイル内であるmain.cからは自由に参照できますが、外部ファイルであるsub.cからは参照できずビルドエラーが発生します。
つまり、
static修飾子は変数ラベルの参照範囲を定義ファイル内へ限定的にする効果があるということです。
大規模な多人数開発になればなるほどstaticの効果は大きくなります。
そもそも、グローバル変数は多数の関数からアクセスできる特性から、ローカル変数よりもシステムへの影響度が大きいのです。
そのグローバル変数を無制限に公開するということは、自分の財布を公園に置いておくようなものです。
もちろん、実際の開発において皆さんが管理するグローバル変数を悪意をもって書き換えてくることはないのですが、公開されているのだから変更してもよいのだろうと考える人はいるわけです。
このような事態を招かないためにも、外部に公開する必要のないグローバル変数はstatic修飾子で保護をするのです。
staticを付けることで、対象のグローバル変数に直接アクセスできるのは、定義対象ファイルに存在する関数達だけに絞られるのですから。
staticの付いたローカル変数
実は関数内で定義するローカル変数にもstatic修飾子を付与することができます。この変数はC言語上で非常に特殊な扱いの変数となるため注意が必要です。
皆さん、このプログラムの流れと実行結果をよく考察してください。ローカル変数で起こりえないことが起きているのがわかります。
関数内で定義されるローカル変数はスタックメモリに配置されると紹介しました。
しかし、
static修飾子が付与されたローカル変数は静的メモリに配置されます。静的メモリに配置されたということは関数呼び出しが終了しても変数ラベルが剥がされないため値を保持することができます。
static付きのグローバル変数とローカル変数の違い
staticが付与された時点で、定義対象の変数は静的メモリに必ず配置されることになります。グローバル変数とローカル変数の違いは参照範囲の違いしかありません。
static付きローカル変数を定義するときの注意ですが、必ず定義と同時に初期化を行ってください。それは初期化するタイミングが他にないからです。
「static付きローカル変数を0で初期化してください」と指示すると代入で0を設定しようとする方がいます。次のプログラムは関数の呼び出し回数をカウントするプログラムですが、初期化と代入で全く異なる動作をします。
念のためもう一度おさらいですが、初期化は変数ラベルが貼られたときに1度だけ実施される設定であり、代入は何度でも実施可能な設定です。
静的メモリはプログラム動作後に1度だけしかラベルが貼られないため、初期化のタイミングは1度しかないのです。
static付きローカル変数は静的メモリに配置される特殊な変数です。初期化は必ず行いましょう!