* OSECPU-ASKA入門 #0016
-(by [[K]], 2013.07.08)
** (0) はじめに
-以下の記事はosecpu118dのWindows版を前提にしています。他の版を使っている場合は適宜読み替えてください。
--ちなみに[[page0074]]を読めばrev2の最新版が見つかります。
~
-もくじ
--[[page0087]]: #0010
--[[page0088]]: #0011
--[[page0089]]: #0012
--[[page0093]]: #0013
--[[page0095]]: #0014
--[[page0098]]: #0015
--[[page0099]]: #0016
** (1) メモリ(配列変数)を使う
-OSECPU-ASKAでは配列変数のことを「メモリ」といいます。そしてメモリには型があります。つまりInt32sでもいいけれど、それ以外の型も使えるということです。
-一例として、メモリを使ってちょっとした絵を描いてみます。
#include "osecpu_ask.h"
#define L_invader LOCAL(0)
LOCALLABELS(1);
do {
Int32s c:R00, x:R01, y:R02, i:R03;
VPtr p:P01;
PLIMM(p, L_invader);
for (y = 0; y != 16; y++) {
for (x = 0; x != 32; x++) {
i = y * 32 + x;
PALMEM0(32, c, T_UINT1, p, i);
c *= 2; // これで0か2になる. ちなみに2は緑色.
api_drawPoint(MODE_COL3, c, x, y);
}
}
}
DAT_SA(L_invader, T_UINT1, 32 * 16);
D1B(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0);
D1B(0,0,0,0, 0,0,0,0, 0,0,0,0, 1,1,1,1, 1,1,1,1, 0,0,0,0, 0,0,0,0, 0,0,0,0);
D1B(0,0,0,0, 0,0,0,0, 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0, 0,0,0,0);
D1B(0,1,0,0, 0,0,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,1,0);
D1B(0,1,0,1, 1,1,1,1, 1,1,0,0, 1,1,1,1, 1,1,1,1, 0,0,1,1, 1,1,1,1, 1,0,1,0);
D1B(0,1,0,1, 1,1,1,1, 1,1,0,0, 1,1,1,1, 1,1,1,1, 0,0,1,1, 1,1,1,1, 1,0,1,0);
D1B(0,1,0,1, 1,1,1,1, 1,1,0,0, 1,1,1,1, 1,1,1,1, 0,0,1,1, 1,1,1,1, 1,0,1,0);
D1B(0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0);
D1B(0,0,0,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,0,0,0);
D1B(0,0,0,1, 1,1,1,1, 1,1,1,0, 0,0,0,0, 0,0,0,0, 0,1,1,1, 1,1,1,1, 1,0,0,0);
D1B(0,0,0,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,0,0,0);
D1B(0,0,0,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,0,0,0);
D1B(0,0,0,0, 0,0,0,0, 1,1,0,0, 0,0,0,0, 0,0,0,0, 0,0,1,1, 0,0,0,0, 0,0,0,0);
D1B(0,0,1,0, 0,0,0,0, 1,1,0,0, 0,0,0,0, 0,0,0,0, 0,0,1,1, 0,0,0,0, 0,1,0,0);
D1B(0,0,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0, 0,0,0,0, 0,0,1,1, 1,1,1,1, 1,1,0,0);
D1B(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0);
DAT_END();
-これを実行するとインベーダの絵が出てきます。これを例にいろいろと命令を紹介したいと思います。
~
-最初の#defineは関数を作った時と同じ構文です。こうやって配列データにラベルを付けます。
-データを宣言しているのは、末尾にあるDAT_SA()...DAT_END();の部分です。これは末尾にしなければいけないということはなく、main関数の前においても平気です。
-DAT_SA(ラベル名, 型名, データの個数); という記述になります。
--使える型名は以下の通りです。
---T_SINT1, T_UINT1
---T_SINT2, T_UINT2
---T_SINT4, T_UINT4
---T_SINT8, T_UINT8
---T_SINT12, T_UINT12
---T_SINT16, T_UINT16
---T_SINT20, T_UINT20
---T_SINT24, T_UINT24
---T_SINT28, T_UINT28
---T_SINT32, T_UINT32
--T_SINT32はInt32sに相当します。
-ここで使っているT_UINT1というのは、1ビットの符号なしの整数型で、0か1しか記憶できません。しかし今回のようなドット絵ではそれで十分なので、これを使いました。
-D1Bというのは、データを記述するための命令でData1Bitの略です。他にD2BやD4Bなどの命令があります。
-結局、これはC言語で言うところの、
int invader[32 * 16] = { 0, 0, 0, 0,... };
-とほぼ同じです。違うのは1ビットの配列だというところだけです。
-注意点としては、総ビット数は8の倍数になっている必要があります。だからT_SINT4を5個、というのはできません(4x5=20で8の倍数になっていない)。ダミーの0を1つ追加して、6個にしてください。
-VPtr p:P01; :
--これは、変数pの宣言で、P01レジスタを割り当てています。Pxxレジスタはポインタレジスタで、このように配列データを指し示すためのものです。なお、P00はリザーブされているので、P01からP27までを自由に使っていくことになります。
--RxxはいつもInt32s型でしたが、PxxはいつもVPtr型になります(void-pointerの略です)。
-PLIMM(p, L_invader); :
--この命令で p = L_invader; になります。こうやってPxxレジスタに入れないと、PALMEM0命令でデータを読めないので、これをやっています。
-PALMEM0(32, c, T_UINT1, p, i);
--C言語風に言えば、 c = p[i]; ということです。
--最初の32は「この処理を32ビットでおこなえ」という意味で、まあ深く考えずいつも32にしておくことをお勧めします。
--T_UINT1はpの型です。iはレジスタ変数である必要があって、定数や式であってはいけません。
--なおMEM0は「メモ」ではなく、「メム・ゼロ」です。つまりオーではなくゼロなので、注意してください。
-do { ... } の末尾にapi_end();を置いてもいいですが、置かなくても大丈夫です。なぜなら間違ってDAT_SA()命令を実行してしまったとしても、何も有害なことは起きずに、DAT_END()のところまで読み飛ばされるからです。
** (2) メモリに書き込む&mallocを使う
-以下のプログラムは、999までの素数を表示するプログラムです。
#include "osecpu_ask.h"
do {
VPtr p:P01;
Int32s i:R00, j:R01, v:R02;
// 初期化.
api_malloc(p, T_UINT1, 1000);
v = 0;
for (i = 0; i != 1000; i++) {
PASMEM0(32, v, T_UINT1, p, i);
}
// 倍数に印をつけていく.
v = 1;
for (i = 2; i != 1000; i++) {
for (j = i * 2; ; j += i) {
if (j >= 1000) break;
PASMEM0(32, v, T_UINT1, p, j);
}
}
// 素数の表示.
for (i = 2; i != 1000; i++) {
PALMEM0(32, v, T_UINT1, p, i);
if (v == 0) {
api_putStringDec('\1 ', i, 3, 1);
}
}
}
-初期値をDAT_SA()構文で設定する必要がない場合、上記の例のようにapi_malloc()でメモリを確保できます。
-PASMEM0()はPALMEM0の読み書きを逆転させたもので、メモリに書くことができます。定数や式の値を書き込むことはできないので、一度レジスタに格納してからPASMEM0してください。
-しかし当然ながら、型に収まらない値を書いてはいけません。それはセキュリティ例外になります(ダメな例:T_UINT1なメモリに2を書こうとするとか)。
--これはセキュリティ上の問題というよりも単なるバグというべきだと思いますが、しかしプログラマは値を確実にメモリに保存したと思ったのに、実は保存できていないわけですから(このメモリを読みこんだらデータが化けてしまう)、意図せずに情報が失われた!というわけで、まあセキュリティ上の問題といえなくもないです。
----
-ここで少しセキュリティ上のポイントを紹介しておこうと思います。
-(a) このプログラムで、初期化処理のPASMEM0をコメントアウトしてしまったらどうなるでしょうか。そうすると、表示処理のところで、初期化されていないメモリを一部読むことになるのですが、これはもちろんやってはいけないことです。OSは他のアプリが解放したメモリを、初期化せずに渡してきたかもしれず、そうなれば他のアプリのデータを盗み見ることが可能になってしまいます。ということで、初期化されていないメモリを読む行為はOSECPU-VMでは許されません。ということでセキュリティ例外になります。
-(b) if (j >= 1000) break; の部分をコメントアウトしたらどうなるでしょうか。そうです、確保した領域をはみ出してアクセスしてしまいます。これもセキュリティ例外になります。
** (3) つづく
-(工事中)
* こめんと欄
#comment