はじめに
ARM RealView Compilation Tools for BREW V1.2 ( RVCTB 1.2 ) による具体的な C++ 開発について解説します。 RVCTB は GCC よりも ARM チップ向けに効率の良いバイナリを生成します。
ARM RealView Compilation Tools for BREW V1.2
SophiaFramework : BREW C++ ライブラリ & GUI フレームワーク & XML ミドルウェア
RVCTB の制約
例外処理や RTTI ( 実行時型情報 )、名前空間が使えないなど、RVCTB は C++ を全てサポートしているわけではありません。
例外処理
RVCTB では、独自の例外処理の仕組みが必要です。たとえば、クラスの演算子の内部でエラー情報を保持し、外部から参照できるようにします。
new 演算子
標準の C++ では、new 演算子でメモリ確保に失敗した場合、例外処理により new 演算子の次のコードは実行されません。( リスト 1 )
▼リスト 1 例外を利用する安全なコーディング try { _buffer = new char[128]; // メモリ確保に失敗した場合, _buffer[0] = 0; // new 演算子の次のコードは実行されない // OK } catch (...) { // NG }
例外処理が使えないと、どうなるのでしょう?( リスト 2 )
new 演算子はメモリ確保に失敗した場合、NULL を返すだけで、new 演算子の次のコードが実行されます。
▼リスト 2 問題のあるコーディング例 _buffer = new char[128]; // メモリ確保に失敗しても _buffer[0] = 0; // new 演算子の次のコードは実行される
例外処理が使えない場合、new 演算子の戻り値に応じて適切な処理が必要です。( リスト 3 )
▼リスト 3 エラー処理がある適切なコーディング例 _buffer = new char[128]; if (_buffer != NULL) { // 通常の処理 } else { // エラー処理 }
new 演算子でメモリ確保に失敗した場合、コンストラクタの起動を回避するコードを記述する必要があります。
オーバーロード演算子
代入演算子の戻り値は自分自身への参照なので、演算子内部でエラーが発生した場合、戻り値としてエラー値を返せないのが問題です。( リスト 4 )
▼リスト 4 一般的な代入演算子 my_string& my_string::operator=(my_string const& param) { // 戻り値は自分自身なのでエラー値を返せない return *this; }
そこで、クラス内部でエラー値を保持し、外部からエラー値を参照できるようにします。( リスト 5 )
▼リスト 5 エラー値を保持する例 my_string& my_string::operator=(my_string const& param) { if (...) { // エラーが発生した場合に,それを _my_exception に記録 _my_exception = true; } return *this; } int main(void) { my_string data; my_string temp; temp = data; // エラーが発生したかどうかを検査 if (temp._my_exception) { // エラー処理 } return 0; }
※ たくさんの例外処理が含まれるコレクションクラスを BREW に移植するのは大変です。 BREW アプリの設計では、例外処理ができるだけ少なくするのがポイントです。
RTTI
RVCTB では、親クラス型の変数から子クラス型の変数へのダウンキャストのための dynamic_cast 演算子や、 実行時の型情報を取得するための typeid 演算子が使えません。( リスト 6 - 7 )
▼リスト 6 RTTI が使える場合 void function(base* p) { if (dynamic_cast<derived_a*>(p) != NULL) { // 変数pはderived_aクラスへのポインタ } else if (dynamic_cast<derived_b*>(p) != NULL) { // 変数pはderived_bクラスへのポインタ } return; }
▼リスト 7 RTTI が使えない場合 void function(base* p) { if (dynamic_cast<derived_a*>(p) != NULL) { // この条件は常に有効になる } else if (dynamic_cast<derived_b*>(p) != NULL) { // この条件は常に無効になる } return; }
dynamic_cast 演算子
dynamic_cast 演算子を使った型情報による条件分岐はできるだけ避け、その代わり仮想関数を使います。( リスト 8 - 9 )
▼リスト 8 好ましくないコード例 class base { }; class derived_a : public base { public: void do_a(void); }; class derived_b : public base { public: void do_b(void); }; void function(base* p) { if (dynamic_cast<derived_a*>(p) != NULL) { dynamic_cast<derived_a*>(p)->do_a(); } else if (dynamic_cast<derived_b*>(p) != NULL) { dynamic_cast<derived_b*>(p)->do_b(); } return; }
▼リスト 9 望ましいコード例 class base { public: virtual void do(void) = NULL; }; class derived_a : public base { public: virtual void do(void); }; class derived_b : public base { public: virtual void do(void); }; void function(base* p) { p->do(); return; }
変数の型が動的に変わらない場合はテンプレートを使います。( リスト 10 )
▼リスト10 テンプレートを使ったコード例 class independent_a { public: void do(void); }; class independent_b { public: void do(void); }; template <typename T> void function(T* p) { p->do(); return; }
typeid 演算子
RVCTB では、typeid 演算子はプリミティブ型や具象クラスにのみ有効で、抽象クラスには使えません。( リスト 11 )
▼リスト 11 RVCTB で正常に動作しない例 class base { public: virtual ~base(void) {} }; class derived : public base { }; int main(void) { base* p = new derived; printf("%s\n", typeid(*p).name()); delete p; return 0; }
抽象クラスの型情報を取得するには、仮想関数や、基底クラスで型情報を保持する変数を使います。( リスト 12 - 13 )
▼リスト 12 仮想関数を使う例 class base { public: virtual int get_typeid(void) const = NULL; }; class derived_a : public base { public: virtual int get_typeid(void) const; }; class derived_b : public base { public: virtual int get_typeid(void) const; }; int derived_a::get_typeid(void) const { return 0x0000000A; } int derived_b::get_typeid(void) const { return 0x0000000B; }
▼リスト 13 基底クラスで型情報を保持する変数を使う例 class base { private: int _typeid; protected: base(int id); public: int get_typeid(void) const; }; class derived_a : public base { public: derived_a(void); }; class derived_b : public base { public: derived_b(void); }; base::base(int id) : _typeid(id) { return; } int base::get_typeid(void) const { return _typeid; } derived_a::derived_a(void) : base(0x0000000A) { return; } derived_b::derived_b(void) : base(0x0000000B) { return; }
その他
RVCTB では、複雑なテンプレートやネームスペースが使えません。
BREW の制約
BREW の最大の制約は、グローバル変数やスタティック変数が使えないことです。
原因
BREW アプリの前提条件
- BREW アプリはプラグインのような働きをする。
- 複数の BREW アプリでひとつのメモリ空間を共有する。
- BREW アプリの実行コードはリードオンリーなメモリ領域に配置される。
コンパイラは、スタティック変数をコード領域とは異なるリードライトな領域に配置します。 そのため、スタティック変数にアクセスするためにコード領域からの相対アドレスは使えません。
BREW のメモリ割り当て
BREW は、図のようにBREW アプリをメモリ上に展開します。
どのアドレスに展開されるかは状況により変化するので、スタティック変数へのアクセスに絶対アドレスは使えません。
また、BREW は SB レジスタを使うので、BREW アプリでは SB レジスタが使えませんが、RVCTB のコンパイルオプションで SB レジスタを変更できます。( リスト 14 )
▼リスト 14 __global_regを利用するコード struct my_global { int i; }; __global_reg(8) my_global* sb; int get(void) { return sb->i; } int main(void) { sb->i = 0; return get(); }
このとき、SB レジスタ以外のレジスタ経由でスタティック変数にアクセスできそうですが、 コールバック関数がレジスタの値を変更するかもしれないので完璧な解決策とはいえません。
利用可能なスタティック変数
スタティック変数の種類
- グローバル変数
- クラススタティック変数
- 関数内スタティック変数
基本的に、BREW アプリではスタティック変数は使えません。( リスト 15 )
▼リスト 15 使用できないスタティック変数の例 class my_counter { public: static int _counter; }; int my_counter::_counter = 0; // 使えない int global_variable = 0; // 使えない int main(void) { static int local_variable = 0; // 使えない return 0; }
スタティック変数でも、プリミティブ型の定数は BREW アプリで使えます。 実行時に書き換えられることがないので、コンパイラがそれらをコード領域に配置するからです。( リスト 16 )
▼リスト 16 使用できる変数 class my_counter { public: static int const _counter; }; int const my_counter::_counter = 0; // 使える int const global_variable = 0; // 使える int main(void) { static int const local_variable = 0; // 使える return 0; }
スタティック変数が使えない影響
BREW では、ANSI C 標準ライブラリや、ARM コンパイラの組込み関数などが使えません。 ANSI C 標準関数やコンパイラ組込み関数のなかには内部でスタティック変数を使うものがあるからです。( リスト 17 )
▼リスト 17 使えない ANSI 標準ライブラリ関数 int main(void) { void* buffer; buffer = malloc(128); // 使えない free(buffer); // 使えない return 0; }
ソフトウェア FPU モード ( ソフトウェアで浮動小数点演算するモード ) でコンパイルすると、浮動小数点の加算は ARM コンパイラ組込み関数の _fadd 関数に置き換えられるので、浮動小数点演算は利用できません。( リスト 18 )
▼リスト 18 ARM コンパイラ組込み関数 int main(void) { double a; double b; double c; a = 1.0; b = 2.0; c = a + b; // _faddに置換される return 0; }
このため、BREW では、ANSI C 標準ライブラリや浮動小数点演算用の関数を移植したヘルパー関数が提供されます。
ヘルパー関数 | 機能 |
---|---|
MALLOC | メモリの確保 |
REALLOC | メモリの再確保 |
FREE | メモリの解放 |
STRSTR | 文字列の検索 |
F_ADD | 浮動小数点の足し算 |
F_SUB | 浮動小数点の引き算 |
標準的な浮動小数点演算を利用できる場合と、できない場合を比較してみます。( リスト 19 - 20 )
▼リスト 19 浮動小数点演算が利用できる場合 int main(void) { double d; float f; // OK int i; d = 1.0; f = 1.5; // OK d += f; // OK i = 2; d += i; // OK return 0; }
▼リスト 20 浮動小数点演算が利用できない場合 int main(void) { double d; float f; // エラー int i; d = 1.0; f = 1.5; // エラー d += f; // エラー i = 2; d = F_ADD(d, FASSIGN_INT(i)); // OK return 0; }
※ BREW では、float 型が使えません。また、浮動小数点演算はすべてヘルパー関数呼び出しにしなければいけません。
BREW アプリで浮動小数点演算を使用する方法
"リターゲット"と呼ぶ、利用する環境に応じて ANSI C 標準ライブラリや ARM コンパイラ組込み関数をカスタマイズする方法があります。
RVCTB のライブラリも BREW 環境に"リターゲット"すれば、BREW でも ANSI C 標準関数や浮動小数点演算が利用可能になります。
浮動小数点演算ライブラリのリターゲット
RVCTB では、ソフトウェア FPU モードでコンパイルすると、 浮動小数点演算を RVCTB 付属 rt_fp.h の ARM コンパイラ組込み関数に置き換えます。( リスト 21 )
▼リスト 21 浮動小数点演算関数の一部 <rt_fp.h> /* * Single-precision arithmetic routines. */ extern __softfp float _fadd(float, float); extern __softfp float _fsub(float, float); extern __softfp float _fmul(float, float); extern __softfp float _fdiv(float, float);
ARM コンパイラ組込み関数のなかには内部でスタティック変数を使うものや、 ソフトウェア割り込みの SWI 命令を使うデバッグ用コードを含むものがあります。
また、浮動小数点演算ライブラリは起動時の初期化関数実行が前提となります。 コンパイラは main 関数の実行前に初期化関数が呼び出されるようにコードを生成します。( リスト 22 )
▼リスト 22 初期化関数 <rt_fp.h> /* * Call this before using any fplib routines, if you're trying to * use fplib on the bare metal. */ extern void _fp_init(void);
浮動小数点演算ライブラリをリターゲットするには、スタティック変数を使う関数やデバッグ用コードが含まれる関数を正しく置き換え、 初期化関数を適切なタイミングで呼び出されるようにします。( リスト 23 )
BREW 環境へのリターゲット
BREW 標準の起動コードに合わせて C 言語を使って、浮動小数点演算ライブラリを BREW 環境にリターゲットします。
※ extern "C" を使えば C++ でもリターゲットできます。
▼リスト 23 リターゲット対象の関数 <rt_misc.h> /* * Redefine this to replace the library's entire signal handling * mechanism in the most efficient possible way. The default * implementation of this is what calls __raise (above). */ void __rt_raise(int /*sig*/, int /*type*/); <errno.h> extern __pure volatile int *__rt_errno_addr(void); <rt_fp.h> /* * This returns a pointer to the FP status word, when it's stored * in memory. */ extern unsigned *__rt_fp_status_addr(void);
BREW アプリには main 関数がありません。 このままでは浮動小数点演算ライブラリの初期化関数 _fp_init が呼び出されません。
そこで、BREW アプリの起動プロセスで呼び出される AEEClsCreateInstance 関数内で _fp_init 関数を呼び出されるようにします。
リターゲット対象である 3 つの関数を独自の実装に置き換えます。
3 つの関数のうちの 2 つは、ライブラリが内部で使う変数プールのアドレスを返す必要があります。 変数プールは環境に合わせて自由に設定可能です。BREWアプリでは、AEEApplet 構造体に変数プールを確保します。
__rt_raise 関数はライブラリ内部で例外が発生した場合に呼び出される関数です。これを何もしないダミー関数に置き換えます。
浮動小数点演算が利用できる、BREW アプリの起動コード ( リスト 24 )
▼リスト 24 浮動小数点演算が利用できる起動コード #if defined __arm #include <rt_misc.h> #include <errno.h> #include <rt_fp.h> #endif // SWI 命令がリンクされないことを保証する #if defined __arm #pragma import(__use_no_semihosting_swi) #endif typedef struct SoftFPApplet { AEEApplet aee; // リターゲット用変数プール #if defined __arm int volatile _errno; unsigned int _fpstatus; #endif } SoftFPApplet; #if defined __arm void __rt_raise(int signal, int type) { return; } int volatile* __rt_errno_addr(void) { SoftFPApplet* ap = (SoftFPApplet*)GETAPPINSTANCE(); return &ap->_errno; } unsigned int* __rt_fp_status_addr(void) { SoftFPApplet* ap = (SoftFPApplet*)GETAPPINSTANCE(); return &ap->_fpstatus; } #endif int AEEClsCreateInstance(AEECLSID ClsId, IShell* pIShell, IModule* po, void** ppObj) { *ppObj = NULL; if (ClsId == AEECLSID_SOFTFP) { if (AEEApplet_New(sizeof(SoftFPApplet), ClsId, pIShell, po, (IApplet**)ppObj, (AEEHANDLER)SoftFP_HandleEvent, NULL) == TRUE) { // 浮動小数点演算ライブラリを初期化する #if defined __arm _fp_init(); #endif // ここにアプリの追加コード return AEE_SUCCESS; } } return EFAILED; }
リスト 25 のようなコードでもリンクエラーになりません。
▼リスト 25 リターゲット後 void function(void) { double d; float f; // OK! int i; d = 1.0; f = 1.5; // OK! d += f; // OK! i = 2; d += i; // OK! return; }
浮動小数点演算以外に、ANSI C 標準ライブラリも BREWアプリで利用可能にできますが、アプリサイズの肥大化に注意が必要です。
BREW ヘルパー関数の実装は BREW 環境に 1 つだけですが、ANSI C 標準関数では利用する関数がアプリにリンクされます。