ポインタを使ってのあれこれ †
プログラム言語でいうところのポインタとは、一般的にはある目的のために確保されているメモリ領域のアドレスを示す値です。
普通にHSPでプログラムを行う際には意識する必要はないものですが、ポインタを介してHSPのシステム内部にアクセスすることにより標準の命令だけではできない処理を行うことが可能になります。
メモリ領域の状態はプログラムの動作に伴い常に変わりつづけるので、さっき有効だったポインタが今も有効であるという保証はありません。
ポインタの取得はポインタを使った処理の直前に行うようにするべきです。
変数に関するポインタ
HSP3 の変数に関する情報はシステムの管理する PVal構造体の中に格納されています。PVal構造体は変数一つにつき一つずつ存在します。
スクリプトから PVal構造体に直接アクセスすることはできませんが、PVal構造体へのポインタ(PValポインタ)を取得することにより間接的にアクセス可能になります。
配列変数の場合でも PVal構造体は変数一つにつき一つです。配列の要素ごとに存在するわけではありません。
ただし配列変数では配列内の特定の要素を指定するために APTR値を使用します。
配列変数のある要素に対する APTR値はその配列変数のの全次元の全要素を一直線に並べたときのその要素の先頭からの位置となります。
PValポインタ(PVal構造体へのポインタ)をスクリプトから直接取得するための命令は用意されていませんが、間接的に取得する方法はあります。
- input 命令で作成された入力ボックスのオブジェクト情報から取得
- #deffunc(#defcfunc) のユーザー定義命令(関数)の引数情報から取得
上記の手法を使って PValポインタを取得するための pvalptr 関数のモジュールです。
+
| | モジュールスクリプト
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
-
|
-
|
-
|
-
|
|
-
|
!
|
!
| #ifndef __MOD_PVAL__
#define __MOD_PVAL__
#module mod_pval
#define prmstack 207
#define global ctype pvalptr(%1) pvalptr_(%1, 0)
#define global ctype getaptr(%1) pvalptr_(%1, 1)
#defcfunc pvalptr_ var _p1, int _p2
mref HSPCTX, 68
dupptr vptr, HSPCTX.prmstack, 8, 4
return vptr(_p2)
#deffunc getpval array _p1, var _p2
dim _p1, 12
dupptr PVAL, pvalptr(_p2), 48, 4
memcpy _p1, PVAL, 48
return
#defcfunc varclone var _p1
dupptr tmpPVAL, pvalptr(_p1), 48, 4
if (tmpPVAL(0) & 0xFFFF0000)=0x00020000 : return 1
if (tmpPVAL(0) & 0xFFFF0000)=0x00010000 : return 0
return -1
#defcfunc bufsize var _p1
dupptr tmpPVAL, pvalptr(_p1), 48, 4
if (tmpPVAL(0) & 0x0000FFFF)=0x00000004 {
return 4
} else : if (tmpPVAL(0) & 0x0000FFFF)=0x00000003 {
return 8
} else : if (tmpPVAL(0) & 0x0000FFFF)=0x00000002 {
tmpAPTR=getaptr(_p1)
if tmpAPTR {
dupptr tmpPD, tmpPVAL(8)+tmpAPTR*4, 4, 4
dupptr tmpSTRINF, tmpPD-24, 24, 4
} else {
dupptr tmpSTRINF, tmpPVAL(7)-24, 24, 4
}
return tmpSTRINF(2)
}
return 0
#global
#endif
|
|
dupptr 命令を使い PVal構造体のクローンを配列変数として作成し、その配列変数にアクセスします。
ただし、その内容を書き換えた場合は HSP の動作が不安定になる可能性がありますので注意してください。
HSP3 の PVal構造体のサイズは48バイトですので
dupptr (変数名), (PValポインタ), 48, 4
とします。(変数(0)から変数(11)までのint型配列変数になります)
+
| | サンプルスクリプト 1
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
| #include "mod_pval.hsp"
mes "整数型の場合 : dim test, 2, 5, 7, 4"
dim test, 2, 5, 7, 4
getpval pvaltest, test
mes "変数の型 (vartype 関数と同じ)
mes ""+(pvaltest(0) & 0xFF)+" ("+vartype(test)+")"
mes "各次元ごとの配列の要素数 (length 系関数と同じ)
mes "一次元 : "+pvaltest(2)+" ("+length(test)+")"
mes "二次元 : "+pvaltest(3)+" ("+length2(test)+")"
mes "三次元 : "+pvaltest(4)+" ("+length3(test)+")"
mes "四次元 : "+pvaltest(5)+" ("+length4(test)+")"
mes "実際の内容へのポインタ (varptr 関数と同じ)
mes strf("%08X", pvaltest(7))+strf(" (%08X)", varptr(test))
mes "実際の内容へのポインタ(配列要素) (varptr 関数と同じ)
mes strf("%08X", pvaltest(7)+4*getaptr(test(1, 3, 6, 0)))+strf(" (%08X)", varptr(test(1, 3, 6, 0)))
mes
mes "実数型の場合 : ddim test, 7, 3, 4, 2"
ddim test, 7, 3, 4, 2
getpval pvaltest, test
mes "変数の型 (vartype 関数と同じ)
mes ""+(pvaltest(0) & 0xFF)+" ("+vartype(test)+")"
mes "各次元ごとの配列の要素数 (length 系関数と同じ)
mes "一次元 : "+pvaltest(2)+" ("+length(test)+")"
mes "二次元 : "+pvaltest(3)+" ("+length2(test)+")"
mes "三次元 : "+pvaltest(4)+" ("+length3(test)+")"
mes "四次元 : "+pvaltest(5)+" ("+length4(test)+")"
mes "実際の内容へのポインタ (varptr 関数と同じ)
mes strf("%08X", pvaltest(7))+strf(" (%08X)", varptr(test))
mes "実際の内容へのポインタ(配列要素) (varptr 関数と同じ)
mes strf("%08X", pvaltest(7)+8*getaptr(test(6, 2, 2, 1)))+strf(" (%08X)", varptr(test(6, 2, 2, 1)))
|
|
モジュール mod_pval.hsp では PVal構造体から情報を取得するための次の関数が定義されています。
- varclone 関数
変数がクローン変数なのか実体のある変数なのかを調べます。
- bufsize 関数
変数用に現在確保されているバッファサイズを取得します。
これらの関数を使ったサンプルスクリプトです。
+
| | サンプルスクリプト 2
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
| #include "mod_pval.hsp"
mes "クローン変数かどうか(varclone) =1:クローン変数"
a=100 : mes strf("a=%-10d", a)+" : varclone(a)="+varclone(a)
b=99.99 : mes strf("b=%-10f", b)+" : varclone(b)="+varclone(b)
c="abcd" : mes "c=\""+c+"\" : varclone(c)="+varclone(c)
dup d, a : mes strf("d=%-10d", d)+" : varclone(d)="+varclone(d)
dupptr e, varptr(b), 8, 3 : mes strf("e=%-10f", e)+" : varclone(e)="+varclone(e)
dupptr f, varptr(c), 4, 4 : mes strf("f=%-10d", f)+" : varclone(f)="+varclone(f)
mes
mes "変数の現在のバッファサイズを取得(bufsize)"
i=100 : mes strf("i=%-11d", i)+" : bufsize(i)="+bufsize(i)
d=99.99 : mes strf("d=%-11f", d)+" : bufsize(d)="+bufsize(d)
sdim st0, 64 : mes "sdim st0, 64 "+" : bufsize(st0)="+bufsize(st0)
sdim st1, 200 : mes "sdim st1, 200"+" : bufsize(st1)="+bufsize(st1)
sdim st2, 777 : mes "sdim st2, 777"+" : bufsize(st2)="+bufsize(st2)
mes
sdim st, 64, 3 : mes "sdim st, 64, 3"
st="0123"
repeat 70 : st(1)=st(1)+cnt\10 : loop
repeat 500 : st(2)=st(2)+""+rnd(10) : loop
repeat 3
mes "st("+cnt+")=\""+st(cnt)+"\""
mes "strlen(st("+cnt+"))="+strlen(st(cnt))+" : bufsize(st("+cnt+"))="+bufsize(st(cnt))
mes
loop
|
|
#deffunc, #defcfunc で定義されるユーザー定義命令・関数の引数に関するポインタ
プログラム中でユーザー定義命令(関数)が呼び出されると、引数の内容に関する情報がシステムの管理する引数スタック領域( HSPCTX::prmstack )に格納されます。
型ごとの詳細は次の通りです。
- int型、double型の引数
引数スタック領域に実引数の値そのものが格納されます。(int型 4バイト、double型 8バイト)
- str型の引数
メモリにバッファ領域が確保され、実引数の内容がそこにコピーされます。
引数スタック領域にはそのバッファ領域へのポインタが格納されます。(4バイト)
- var型、array型の引数
引数スタック領域に実引数の PValポインタ値、APTR値が格納されます。(8バイト)
- local 引数
引数スタック領域に PVal 構造体が格納されます (48バイト)。
- modvar 引数 ( thismod )
#modfunc, #modcfunc の第一引数には、モジュール変数を管理する MPModVarData? 構造体(12byte)が積まれます。これは、PVal ポインタ値と APTR 値の他に、モジュール変数の種類も積まれています。
- modinit, modterm 引数
#modinit, #modterm の第一引数も、#modfunc などと同じです。
引数スタック領域に代入された内容、str型のバッファ領域の双方とも一時的なもので、ユーザー定義命令(関数)の処理が終了すると( return で戻ると )無効になります。
前述の事柄よりユーザー定義命令(関数)の引数へのポインタは、引数スタック領域内の該当する個所へのポインタということになります。
具体的には命令(関数)が呼び出された直後の HSPCTX 構造体の prmstack メンバの値から算出します。
HSPCTX.prmstack = 引数 1 に関する情報
HSPCTX.prmstack + 引数 1 に関する情報のサイズ = 引数 2 に関する情報
HSPCTX.prmstack + 引数 2 に関する情報のサイズ = 引数 3 に関する情報
...
ポインタを使って引数にアクセスするサンプルです。
+
| | サンプルスクリプト
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
| #module
#define prmstack 207
#deffunc test int _i, var _v, array _a, str _s, double _d
mref ctx, 68
dupptr i, ctx.prmstack, 4, 4
mes i
dupptr ptr, ctx.prmstack+4, 8, 4
dupptr pval, ptr(0), 48, 4 mes strf("型 : %1d", pval(0) & 0xFF)+strf(" 配列一次元要素 : %2d", pval(2))
dupptr ptr, ctx.prmstack+12, 8, 4
dupptr pval, ptr(0), 48, 4 mes strf("型 : %1d", pval(0) & 0xFF)+strf(" 配列一次元要素 : %2d", pval(2))
dupptr ptr, ctx.prmstack+20, 4, 4
dupptr s, ptr, 64, 2
mes s
dupptr d, ctx.prmstack+24, 8, 3
mes d
return
#global
v=65535.32768
a=127, 255
test 100, v, a(0), "ABC", 99.9999
mes
v="string"
a=0, 1, 2, 3, 4
c="01234"
test -10000, v, a(1), c, 100.001
|
|
PVal構造体はひとつの変数につき一つの構造体で管理していますが、配列変数の要素など、変数の実体はSTRBUF構造体で要素ごとに管理されていることがあります。
特に可変長データである文字列配列を管理するのに、文字列そのものを連続した領域内( 配列 )で管理するのは都合が悪いです。
そこで文字列の実体は各々別の場所に確保し、PVal構造体は様々な場所に散らばったひとつ変数で参照される文字列を、文字列へのポインタ配列で管理することで、多次元文字列配列を実現しています。
この配列はPVal構造体の master メンバにより参照され、実際には文字列配列ではなく文字列へのポインタ配列となります。
以上をまとめると、変数の実体を管理するのはSTRBUF構造体で、STRBUF構造体を管理しているのがPVal構造体であるといえます。
+
| | 文字列配列のマスターテーブル
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
| #include "mod_pval.hsp"
#module
#deffunc disp array p1
mes "flag = "+ wpeek(p1(0), 0)
mes "exflag = "+ wpeek(p1(0), 2)
mes "myblock = "+ p1(1)
mes "size = "+ p1(2)
mes "ptr = "+ strf("0x%08X", p1(3))
mes "extptr = "+ strf("0x%08X", p1(4))
mes "opt = "+ strf("0x%08X", p1(5))
dupptr data, p1(3), p1(2), 2
mes "data = "+ data
return
#global
s = "abc"
ss = "naznyark", "kz3"
dupptr pval, pvalptr(s), 48
dupptr strbuf, pval(7) - 24, 88
disp strbuf
dupptr pval, pvalptr(ss), 48
dupptr master, pval(8), pval(6) : master(0) = pval(7)
repeat pval(6) / 4
dupptr strbuf, master(cnt) - 24, 88
mes "----"
disp strbuf
loop
stop
|
|
- mod_pvalptrを使った文字列多次元配列のテキスト保存作ってみました。パラメータのチェックはしていません。PVal構造体のmasterポインタを参照していますが、どうせなら先頭要素へのポインタもmasterテーブルに載せてくれると、先頭要素を特別扱いせずに済むのにな...あと、サポート外の"%s"使ってます。サポート外なのにエラーを出さずにヌルチェックというのは...^^; -- kz3
- と言っても仕方ないので自分で先頭インデックスに書き込むようにしました。 -- kz3
- strf はサポート外の "%s" が動作しているのではなくて、第2引数が文字列だとその文字列をそのまま返すようです。 -- naznyark?
1
2
3
4
5
6
7
8
|
| i = 9999 : s = "abc"
mes strf("i : %d", i)
mes strf("s : %d", s)
mes strf("s : %s", s)
mes strf(" : %d", "123")
mes strf(" : %s", "123")
mes strf("","123")
|
- そうなんですよね。 -- kz3
- モジュールmod_pvalptrはポインタだけでなくpvalのあらゆる情報を取得できるのでmod_pvalって端折ってもいいかなと思ったりです^^が、getpval命令でp1がarrayになってますが、これはvarでもいいですよね? -- kz3
- > モジュールmod_pvalptrはポインタだけでなくpvalのあらゆる情報を取得できるのでmod_pvalって端折ってもいいかなと思ったりです
それもそうですね。というわけで名前変更しました。
> getpval命令でp1がarrayになってますが、これはvarでもいいですよね?
varでもいいです。でも動作が異なる場合があるのでどっちがいいかな(悩)・・・? -- naznyark?
(参考)varとarrayの違い。配列の先頭要素以外の要素を渡して型変換を伴う処理が行われる場合。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
| #module
#deffunc test_v var _p1
_p1=100, 200 : return
#deffunc test_a array _p1
_p1=100, 200 : return
#global
s="ab", "cd" : test_v s(0)
mes ""+s(0)+","+s(1)
s="ab", "cd" : test_a s(0)
mes ""+s(0)+","+s(1)
s="ab", "cd" : mes ""+s(0)+","+s(1)
s="ab", "cd" : test_a s(1)
mes ""+s(0)+","+s(1)
|
- こういう使い分けはどうかな? -- kz3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
| #module
#deffunc _dimv var p1, int p2
mes p1
dim p1, p2
return
#deffunc _dima array p1, int p2
mes p1
dim p1, p2
return
#global
hoge = 3 : _dimv hoge, 10
hoge.2 = 33: _dimv hoge.2, 10
piyo = 5 : _dima piyo, 10
piyo.4 = 55: _dima piyo.4, 10 piyo.0 = 66: _dima piyo.4, 10
|
- あ、arrayだと要素を渡しても渡されるのは常に先頭?みたいですね。 -- kz3
- モジュールリストにpvalモジュールへリンク追加しておきました。ページ名、pvalほにゃららに変えたほうがいいでしょうか? -- kz3
- お手数かけます。ページ名は今のままでいいと思いますよ。 -- naznyark?
- おっけいですー。 -- kz3
- arrayは配列渡し、varは変数要素渡しです。
だから var でとったものは配列として使えない。 --