2.5.1. C/C++ プログラムでのライブラリ関数の使用方法

このセクションでは、以下の内容について説明します。

実行環境の初期化とアプリケーションの実行

プログラムのエントリポイントは C ライブラリ内の __main です。この位置ではライブラリコードが以下の処理を実行します。

  1. 非ルート(RO および RW)実行領域をロードアドレスから実行アドレスにコピーします。 また、圧縮されているデータセクションがある場合は、これらのデータセクションをロードアドレスから実行アドレスに伸張します。 詳細については、『リンカ / ユーティリティガイド』を参照して下さい。

  2. ZI 領域をゼロにします。

  3. __rt_entry に分岐します。

ライブラリによる上記の動作が必要でない場合には、Example 2.1 に示すように、__rt_entry に分岐するユーザ定義の __main を使用して抑止できます。

Example 2.1. __main と __rt_entry

    IMPORT __rt_entry	
    EXPORT __main	
    ENTRY	
__main
    B     __rt_entry	
    END

ライブラリ関数 __rt_entry() は、以下のようにプログラムを実行します。

  1. __rt_stackheap_init() を呼び出して、スタックとヒープをセットアップします。

  2. __rt_lib_init() を呼び出して、参照されるライブラリ関数およびロケールを初期化します。さらに必要であれば、main()argcargv をセットアップします。

    C++ の場合は、__cpp_initialize__aeabi_ を使用して任意のトップレベルオブジェクトのコンストラクタを呼び出します。 詳細については、「C++ の初期化、構築、および破棄」を参照して下さい。

  3. アプリケーションのユーザレベルルートである main() を呼び出します。

    main() で実行される処理の一環として、ライブラリ関数への呼び出しが実行される場合があります。 詳細については、main() から呼び出されるライブラリ関数を参照して下さい。

  4. main() によって返された値を使用して exit() を呼び出します。

C++ の初期化、構築、および破棄

C++ では、スタティックな保存期間を持つオブジェクトに対する構築と破棄に関してある程度の要件が設けられています。 『C++ Standard』のセクション 3.6 を参照して下さい。

ARM C++ コンパイラでは、.init_array 領域を使用してこの目的を達成します。 これは、関数に対する自己相対ポインタの const データ配列です。 例えば、以下の C++ 変換単位がファイル test.cpp に含まれているとします。

struct T
{
    T();
    ~T();
} t;

int f()
{
    return 4;
}

int i = f();

これは、以下の擬似コードに変換されます。

         AREA ||.text||, CODE, READONLY
     int f()
    {
         return 4;
    }

     static void __sti___8_test_cpp
     {
         // construct 't' and register its destruction
         __aeabi_atexit(T::T(&t), &T::~T, &__dso_handle);
         i = f();
     }
         AREA ||.init_array||, DATA, READONLY
         DCD __sti___8_test_cpp - {PC}

         AREA ||.data||, DATA
     t   % 4
     i   % 4

この擬似コードは説明用であり、実際に使用されているものではありません。 生成されたコードを表示するには、armcc -c --cpp -S を使用して C++ ソースコードをコンパイルして下さい。

リンカによって、各 .init_array がさまざまな変換単位から収集されます。 .init_array は同じ順序で収集することが重要です。

ライブラリルーチン __cpp_initialize__aeabi_ は、main が呼び出される前に、C ライブラリスタートアップコード __rt_lib_init から呼び出されます。 __cpp_initialize__aeabi_ は、該当する関数を呼び出しながら .init_array を順番に実行します。 終了時には、__rt_lib_shutdown によって __cxa_finalize が呼び出されます。

通常の場合、最高でも T::T() (マングル名 _ZN1TC1Ev)に対して 1 つの関数、T::~T() (マングル名 _ZN1TD1Ev)に対して 1 つの関数、1 つの __sti__ 関数、および各変換単位に対して 4 バイトの .init_array のみが存在します。 関数 f() のマングル名は _Z1fv です。 変換単位間の初期化順を決定する方法はありません。

デストラクタを含む、関数のローカルなスタティックオブジェクトも、__aeabi_atexit を使用して処理されます。

セクションによっては、同じ領域内に連続して配置されていないとベース / リミットシンボルにアクセスできない場合もあります。 アクセスできない場合、リンカによってエラーが生成されます。

例えば、C++ コンパイラによって .init_array 領域が生成されます。 この領域は、__cpp_initialize__aeabi_ と同じ領域に配置する必要があります。 これを行うには、例えば以下のように分散ロード記述ファイル内でセクションを指定します。

LOAD_ROM 0x0000000
{
    EXEC_ROM 0x0000000
    {
        init_aeabi.o(+RO)
        * (.init_array)
        ...
    }
    RAM 0x0100000
    {
        * (+RW,+ZI)
    }
}
従来のサポート

RVCT v2.1 以降では、C$$pi_ctorvec の代わりに .init_array が使用されています。 ただし、C$$pi_ctorvec を含むオブジェクトは引き続きサポートされています。 このため、従来のオブジェクトを使用している場合、分散ファイルの内容は以下のようになります。

LOAD_ROM 0x0000000
{
    EXEC_ROM 0x0000000
    {
        init_aeabi.o(+RO)
        init.o(+RO)              ; backwards compatibility
        * (.init_array)
        * (C$$pi_ctorvec)        ; backwards compatibility
        ...
    }
    RAM 0x0100000
    {
        * (+RW,+ZI)
    }
}

RVCT の C++ の名前マングルスキームは、ADS v1.2 とは異なります。 新しい C++ イメージを古いデバッガにロードする際に、この相違が影響する場合があります。

例外システムの初期化

例外システムは、オンデマンドで(つまり最初の使用時)、または main が呼び出される前に初期化できます。 オンデマンドによる初期化には、例外システムが使用されない限りヒープメモリが割り当てられないという利点があります。ただし、ヒープが最初の使用時に不足したときに例外(std::bad_alloc など)を処理できないという欠点もあります。

デフォルトは、オンデマンドでの初期化です。 main が呼び出される前に例外システムを初期化するには、以下の関数をリンクに含めます。

extern "C" void __cxa_get_globals(void);
extern "C" void __ARM_exceptions_init(void)
{
    __cxa_get_globals();
}

__cxa_get_globals の呼び出しはコードに直接配置できますが、この呼び出しを __ARM_exceptions_init に配置すると、呼び出しが可能な限り早期に実行されるようになります。 つまり、グローバル変数が初期化され、main に入る前に呼び出されます。

__ARM_exceptions_init はライブラリ初期化メカニズムによって弱く参照されます。また、__rt_lib_init の一部として存在する場合に呼び出されます。

Note

例外システムは、例えば std::set_terminate() などのさまざまなライブラリ関数の呼び出しによって初期化されます。 このため、main が呼び出される前に必ずしも初期化を行う必要はありません。

例外用の緊急時バッファメモリ

ヒープが不足したときに std::bad_alloc 例外処理の実行に使用する緊急時メモリを割り当てるかどうかを選択できます。

緊急時メモリを割り当てるには、シンボル __ARM_exceptions_buffer_required をリンクに含める必要があります。 次に、例外システムの初期化の一部として、__ARM_exceptions_buffer_init() が呼び出されます。 デフォルトではシンボルは含まれていません。

以下のルーチンは、例外用の緊急時バッファを管理します。

extern "C" void *__ARM_exceptions_buffer_init()

緊急時バッファメモリを割り当てるために、実行時の間に一度呼び出されます。 ルーチンからは、緊急時バッファメモリのポインタ、またはメモリが割り当てられない場合には NULL が返されます。

extern "C" void *__ARM_exceptions_buffer_allocate(void *buffer, size_t size)

このルーチンは、例外処理が実行される直前に、例外オブジェクトに割り当てる十分なヒープ メモリがない場合に呼び出されます。buffer は、__ARM_exceptions_buffer_init() によって事前に返された値、または、ルーチンが呼び出されなかった場合には NULL となります。__ARM_exceptions_buffer_allocate() は、8 バイト境界で整列される size バイトのメモリへのポインタ、または、割当できなかった場合には NULL を返します。

extern "C" void *__ARM_exceptions_buffer_free(void *buffer, void *addr)

__ARM_exceptions_buffer_allocate() によって割り当てられた可能性のあるメモリを解放するために呼び出されます。buffer は、__ARM_exceptions_buffer_init() によって事前に返された値、または、そのルーチンが呼び出されなかった場合には、NULL になります。 このルーチンは、渡されたアドレスが緊急時バッファメモリから割り当てられたものであるかどうかを判断します。緊急時バッファメモリによって割り当てられたメモリと判断された場合には、このメモリを適切に解放してから NULL 以外の値を返します。 addr のメモリが __ARM_exceptions_buffer_allocate() によって割り当てられたものではない場合は、ルーチンは NULL を返します。

これらのルーチンのデフォルト定義はイメージにありますが、独自の定義を設定してライブラリに含まれているデフォルトを上書きすることもできます。 デフォルトのルーチンでは、1 つの std::bad_alloc 例外オブジェクトしか入らない容量が予約されます。 緊急時バッファが必要ない場合は、これらのルーチンをすべて再定義して、NULL のみを返すのが間違いのない方法です。

main() から呼び出されるライブラリ関数

関数 main() はアプリケーションのユーザレベルルートです。 この関数を実行するには、実行環境が初期化されており、入出力関数を呼び出せる状態になっている必要があります。 プログラムは main() の中で、ユーザによるカスタマイズが可能な C ライブラリ内の関数を呼び出す、以下のいずれかの処理を実行する場合があります。

Copyright © 2007 ARM Limited. All rights reserved.ARM DUI 0349AJ
Non-Confidential