So-net無料ブログ作成
検索選択

Keil の 8051 コンパイラは、変数をどこに割り当てるのか。 [PSoC]このエントリーを含むはてなブックマーク#

変数宣言あれこれ

PSoC CreatorPSoC 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ボード+デバッグ・ボード (トライアルシリーズ)

シリーズ最強!PSoC3ボード+デバッグ・ボード (トライアルシリーズ)

  • 作者: 古平 晃洋
  • 出版社/メーカー: CQ出版
  • 発売日: 2012/05
  • メディア: 単行本

nice!(0)  コメント(1)  トラックバック(0)  このエントリーを含むはてなブックマーク#

nice! 0

コメント 1

ectoyfan

うっかりquick sortも書けないのかあ.うひゃあ.
PSoC1の「PullUp/Downが勝手に外れるGPIO」クラスのどつぼ仕様.
by ectoyfan (2015-08-19 21:27) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この記事のトラックバックURL:
※言及リンクのないトラックバックは受信されません。