Cのプログラムで直アドレスを関数呼び出し
Cのプログラムから任意の番地をコールするような場合、普通は関数ポインタを使います。
ですが、関数ポインタを使うと、指すアドレスを示すためにRAM領域を使ってしまいます。
たとえば、
------------------------------
typedef void (*functype)(void);
functype func = (functype)0x12345678;
func();
------------------------------
とすると、
Intel x86アーキテクチャではBorlandのCコンパイラで次のようなアセンブラに変換されました。
------------------------------
mov [ebp-0x94], 0x12345678
call dword ptr [ebp-0x94]
------------------------------
このように、スタックに作った変数にジャンプ先のアドレスを入れてからジャンプしています。
そこで、次のような記述でもできるようです。
------------------------------
((void (*)())0x12345678)();
------------------------------
変な文ですが、次のようなアセンブラに変換されました。
------------------------------
mov edx.0x12345678
call edx
------------------------------
メモリではなくレジスタに入れているので、結局同じだと思うかもしれません。
さて、この文
((void (*)())0x12345678)();
は、0x12345678という数値を(void (*)())という関数ポインタ型にキャストし、それをすぐに呼び出しているわけです。
引数や戻り値を取る場合には、
((bool (*)(int a,char *str))0x12345678)(1,"ABC");
というふうにできるようです。
さらにdefine文でマクロ定義してしまえば、
#define funcx(A,STR) ((bool (*)(int a,char *str))0x12345678)((A),(STR))
まるでbool funcx(int a,char *str)という関数のように使えます。
これが何の役に立つかというと、組み込みシステムにおいて、ユーザアプリとファームウェアを別々に作る場合、ファームウェアの各種APIをC言語の形式で用意したとします。そして、ファームウェアのAPI関数を特定の番地に配置します。たとえば、コンソールに1文字表示するルーチンを0x40000000番地におくとか。
ユーザアプリがそれらのAPI関数を呼び出す場合に、普通ならアドレス解決のために何らかのファイルをリンクしなければなりません。GCCならばリンカスクリプトにアドレス解決のための記述するということもできます。例えば、H8/OSではAPI関数のアドレス解決をリンカスクリプトでやっているようです。
ところが、この方法だとヘッダファイルをインクルードするだけで済むので、そのあたりが楽になります。
コンパイラがうまく最適化してくれてコール先のアドレスをイミディエイトにしてくれれば、スタックに値を積まなくてもよくなるので、メモリの節約になります。
ある組み込みCPU向けに、リンカスクリプトを使わずにAPIを作るにはどうすればよいかと悩んでこうしました。
いろんな言語があるけれども、やっぱりC/C++が一番面白いです。
| 固定リンク


コメント