* OSECPU-ASKA入門 #0014 -(by [[K]], 2013.06.26) ** (0) はじめに -以下の記事はosecpu115dのWindows版を前提にしています。他の版を使っている場合は適宜読み替えてください。 -以下の記事はosecpu117dのWindows版を前提にしています。他の版を使っている場合は適宜読み替えてください。 --ちなみに[[page0074]]を読めばrev2の最新版が見つかります。 ~ -もくじ --[[page0087]]: #0010 --[[page0088]]: #0011 --[[page0089]]: #0012 --[[page0093]]: #0013 --[[page0095]]: #0014 ** (1) 関数の作り方、呼び出し方 -今回は予定とは違った順番なりますが、関数の話を書きます。 -与えられた二つの数字を加算して結果を返す関数を作って呼んでみたいと思います。 -まずは関数名を決めます。・・・funcにしますね。 -じゃああとは成り行きで作ってみます。 #include "osecpu_ask.h" #define L_func LOCAL(0) LOCALLABELS(1); #define func(_r, a, b) R30=a; R31=b; CALL(L_func); _r=R30 // main do { func(R00, 2, 3); api_fillRect(MODE_COL3, R00, 16, 16, 0, 0); } api_end(); // これがないと下の関数を再度実行して無限ループになってしまう. beginFunc(L_func); do { Int32s a:R30, b:R31, r:R30; r = a + b; } endFunc(); -これを実行すると、画面にピンクの四角が表示されるはずです。つまりR00は5になったのです。 ~ -さて、それでは一つずつ説明していきます。 -まず do { ... } ですが、これの実体は1度だけ実行するfor文です。それは結局こんなのを書かなくても同じことなのですが、これがあれば変数宣言がスコープの外に出てしまう心配がありません。 --C言語なら単に { ... } と書くところですよね。ASKAはおバカなのでまだそれができないのです。どうもすみません。毎度ですが、おもいやりと優しさでお願いします。 -関数を作るときは、"L_"を頭につけた定数を#defineする必要があります。こういうのも本当は自動でやって隠してくれたら分かりやすいのですが、現状はASKAがおバカで・・・以下略。 -そして#defineの内容はLOCAL(0)とかLOCAL(1)とか、そういうものを使います。番号はシリアルナンバーみたいなもので、まあ適当でいいのですが、あまり大きな数字は指定しないでください。同じ番号を二度指定してはいけません。0以上の整数でお願いします。 -LOCALLABELS(?);は、LOCAL(?)で付けた番号の最大値+1をASKAに教えてやるための構文です。これがないとASKAは内部でラベル番号をうまく管理できなくなってしまいます。 -さて次はこれです: #define func(_r, a, b) R30=a; R31=b; CALL(L_func); _r=R30 -これは、関数呼び出しをかっこよく見せるための#defineです。 --OSECPU-VMでは引数はR30~R3Bを使って渡します。そして結果をR30~R3Bを使って返します。 --これに逆らってもいいのですが、その場合はいろいろと面倒になるかもしれません。 --註:C言語などとは違って、複数の値を返せます。これはちょっと面白いですよね。 -関数funcの中身ですが、この中ではR30とR31しか使っていません。しかし、この中で他のレジスタを自由に使うことができます。 -そしてR00~R1Fに関しては、いろいろ使って適当な値を代入してしまったとしても、呼び出し元に影響を与えることはありません。安心して使ってください。 --違う書き方をすると、R00~R27までを自由に使えるようになります。 ** (2) 再帰呼び出しの例 -次は階乗を計算するプログラムの例です。 #include "osecpu_ask.h" #define L_func LOCAL(0) LOCALLABELS(1); #define func(_r, a) R30=a; CALL(L_func); _r=R30 // main do { Int32s ans:R00; func(ans, 10); api_putStringDec('\1', ans, 10, 1); // 「OSECPU-ASKA入門 #0015」で解説します. } api_end(); // これがないと下の関数を再度実行して無限ループになってしまう. beginFunc(L_func); do { Int32s _a:R30, _r:R30, a:R00, b:R01; a = _a; // R30以降のレジスタは、関数呼び出しやAPI呼び出しをすると壊れてしまうので、a(=R00)に保存する. if (a > 1) { b = a - 1; func(b, b); // b = func(a - 1); a *= b; // a = a * func(a - 1); } _r = a; } endFunc(); ** (3) 関数呼び出しに関する中級テクニック -関数呼び出しにおいては、引数をR30以降に格納するのが基本なのですが、実はこれは義務ではなく、R00~R1Fに引数を入れて渡すこともできます。この場合、関数の中でこれらの値を変更してしまったとしても、呼び出し元には影響しません。これについては(1)のところで説明したとおりです。 -ただし、R00~R1Fの値を変更しても呼び出し元に影響できないということは、これらのレジスタを使って返値を渡すことはできないということも意味します。したがって、返値に関してはR30以降を使うしかありません。 #include "osecpu_ask.h" #define L_func LOCAL(0) LOCALLABELS(1); #define func_R00_R30() CALL(L_func) // R00で値を渡す. R30で値を返す. // main do { Int32s ans:R00; R00 = 10; func_R00_R30(); ans = R30; api_putStringDec('\1', ans, 10, 1); } api_end(); // これがないと下の関数を再度実行して無限ループになってしまう. beginFunc(L_func); do { Int32s _r:R30, a:R00, b:R01; _r = a; if (a > 1) { b = a; a--; func_R00_R30(); _r *= b; // _r = a * func(a - 1); } } endFunc(); ** つづく -[[page0098]]につづく * こめんと欄 #comment