Keil の 8051 コンパイラは、変数をどこに割り当てるのか。 [PSoC]
PSoC Creator で PSoC 3 のプログラムを作成する時、デフォルトでは Keil の 8051 コンパイラが使われます。 この記事では、このコンパイラが、どのように変数をメモリに割り当てるのかを調べます。
大域変数の割り当て
C のコンパイル単位は、 C のソースファイルです。 ソースファイルには、変数と関数を記述する事が出来ます。 関数の外にある変数を大域変数 (global variable) と呼んでいます。 たとえば、以下のように変数を宣言します。
#include <project.h> static uint16 static_var; uint16 global_var; static uint16 static_var_init = 0x1234; uint16 global_var_init = 0x1234; int func() {
大域変数は、デフォルトの状態では別のコンパイル単位から "extern" 修飾子を使ってアクセスする事が出来ます。 別のコンパイル単位からのアクセスを禁止するためには、 "static" 修飾子を使って、このコンパイル単位だけがアクセス出来るようにします。 このアクセス可能な範囲の事をスコープ (scope) と呼んでいます。
メモリ上の変数の割り当て場所は、 MAP ファイルに記述されています。
VALUE REP CLASS TYPE SYMBOL NAME ==================================================== 02000056H PUBLIC XDATA WORD global_var 02000058H PUBLIC XDATA WORD global_var_init 0200005AH SYMBOL XDATA WORD static_var 0200005CH SYMBOL XDATA WORD static_var_init
これらの変数は、永続的に使用されますので、他の変数とは重ならない独立したアドレスに割り当てられます。 PUBLIC と表示されている変数が、別のコンパイル単位からもアクセス可能な変数です。 初期値を設定したものとしていないもので、区別することなくメモリ上に割り当てられていますが、これは、初期値を設定していない大域変数はゼロに初期設定されるので、すべての領域が初期設定される事に代わりがないからです。
局所変数の割り当て
関数の内部で宣言された変数は、局所変数と呼ばれます。 これらのスコープは、関数内に限られますので、別の場所で宣言された名前との衝突を気にする必要がありません。
int func() { uint16 default_local_var; auto uint16 auto_local_var; static uint16 static_local_var; static_var++; static_var_init++; { auto uint16 auto_block1_var = 0x2345; default_local_var++; auto_local_var++; static_local_var++; auto_block1_var++; } { auto uint16 auto_block2_var = 0x3456; default_local_var++; auto_local_var++; static_local_var++; auto_block2_var++; } return auto_local_var; }
局所変数は、デフォルトでは自動変数 (auto variable) と呼ばれる変数として宣言されます。 自動変数を明示的に指定するには、 "auto" を付けます。
静的変数を宣言するために "static" を付けます。 この "static" 修飾子は、大域変数で使用した "static" がスコープを表していたのとは意味が異なります。
では、 "auto" と "static" の違いは何かと言うと、 "auto" は、一時的にメモリが割り当てられるのに対して、 "static" の方はメモリ上に永続的に存在し続けます。 このため、 "static" で宣言された変数の内容は、関数を出た後でも保存されます。
自動変数は、カーリーブラケット{}で囲まれたブロック内部で宣言する事も出来ます。 この場合、変数のスコープはブロックの内部だけです。
これらの変数は、以下のように割り当てられます。
VALUE REP CLASS TYPE SYMBOL NAME ==================================================== 02000052H SYMBOL XDATA WORD static_local_var 0200005EH SYMBOL XDATA WORD default_local_var 02000060H SYMBOL XDATA WORD auto_local_var 02000062H SYMBOL XDATA WORD auto_block1_var 02000062H SYMBOL XDATA WORD auto_block2_var
"static" 宣言された変数の格納場所は、大域変数と同じ場所が使われます。 これに対して、自動変数は動的に格納場所を確保するため、スタックに配置されるのが一般的です。 ところが、 8051 CPU の場合、スタックの容量が小さいため、スタックを使わずに固定アドレスに自動変数を配置します。
アドレスマップ上では、 0200005EH から上のアドレスが、自動変数のために確保される領域となっています。
ブロック内で宣言された変数 auto_block1_var と auto_block2_var は、どちらも同じアドレスに割り当てられています。 これは、これらの変数のスコープ範囲が重なっていないので、同時に使用される事がないためです。 このように、同一アドレスのメモリを複数の自動変数で共有することで、実際に必要なメモリ容量を減少させています。
int func_2nd() { auto uint16 auto_local_2nd_var; auto_local_2nd_var++; return auto_local_2nd_var; }
この自動変数によるメモリの共有は、複数の関数の間でも行われます。 例えば、上のような自動変数の宣言を含んだ関数を作成してみます。
VALUE REP CLASS TYPE SYMBOL NAME ==================================================== 0200005EH SYMBOL XDATA WORD auto_local_2nd_var
すると、変数 auto_local_2nd_var は、関数 "func()" の default_local_var と同じアドレス 0200005EH に割り当てられています。 これも、双方のスコープが重ならない事を利用したものです。
スコープが重なるかも知れない時は
以上の関数は、理論的にスコープが重ならない事が自明な場合の変数割り当てです。 ところが、割り込み処理ルーチン内で関数が使用される場合、スコープの入れ子が可能になるので、同じメモリを共有する事が出来なくなってしまいます。
このような場合には、自動変数をスタック上に割り当てさせるための修飾子 CYREENTRANT を使用します。
int func_reent() CYREENTRANT { uint16 reent_default_local_var; auto uint16 reent_auto_local_var; static uint16 reent_static_local_var; global_var++; global_var_init++; { auto uint16 reent_auto_block1_var = 0x2345; reent_default_local_var++; reent_auto_local_var++; reent_static_local_var++; reent_auto_block1_var++; } { auto uint16 reent_auto_block2_var = 0x3456; reent_default_local_var++; reent_auto_local_var++; reent_static_local_var++; reent_auto_block2_var++; } return reent_auto_local_var; }
このように関数を宣言すると、 "static" 宣言された変数以外は、スタック上に配置されます。
VALUE REP CLASS TYPE SYMBOL NAME ==================================================== 02000054H SYMBOL XDATA WORD reent_static_local_var 00000000H SYMBOL XSTK WORD reent_default_local_var 00000002H SYMBOL XSTK WORD reent_auto_local_var 00000004H SYMBOL XSTK WORD reent_auto_block1_var 00000004H SYMBOL XSTK WORD reent_auto_block2_var
スタック上に配置される変数同士でも、 reent_auto_block1_var と reent_auto_block2_var のようにスコープが重ならないものは、同一メモリに割り当てられます。
8051 CPU では、スタックの容量が小さいためにデフォルトで変数が固定アドレスに割り当てられていましたが、通常のコンパイラでは、自動変数はスタックに割り当てられます。
プロジェクトアーカイブ
この記事を書くために作成したプロジェクトは、このファイルの拡張子を ZIP に変更して展開すると再現できます。
関連文献
シリーズ最強!PSoC3ボード+デバッグ・ボード (トライアルシリーズ)
- 作者: 古平 晃洋
- 出版社/メーカー: CQ出版
- 発売日: 2012/05
- メディア: 単行本
うっかりquick sortも書けないのかあ.うひゃあ.
PSoC1の「PullUp/Downが勝手に外れるGPIO」クラスのどつぼ仕様.
by ectoyfan (2015-08-19 21:27)