Full BASICの急所(関数と副プログラム)


関数と副プログラムの変数引数

関数と副プログラムは別物です。

Full BASICでは,関数の引数はすべて値渡しです。引数に変数を書いても,関数の副作用で変数値が変わってしまうことはありません。

一方,副プログラムと絵定義に変数を引数として書くと,参照渡しになります。これは,副プログラムまたは絵定義の実行中に引数の値を書き換えると,実引数の値が変わることを意味します。 Full BASICでは値が複数個の数値であるような関数の定義ができないので,その場合には,関数ではなく,副プログラムを使うことになります。

この違いは,見かけでは分からないので,初心者には落とし穴になります。つまり,関数定義の変数引数に参照渡しの動作を期待したり,あるいは,反対に,関数での経験から副プログラム中で引数の値を変更するプログラムを書いてしまい,副作用によるバグに悩むことがあります。
つまり,Full BASICでは,手続き定義で引数の値を変えるプログラムを書くことが許容されているのですが,特に必要な場合以外は,引数の値を変えるようなプログラムを書くのは避けておいたほうがいいといえます。

なお,副プログラムおよび絵定義で引数を値渡しにしたい場合は,変数名の前後を括弧でくくることで可能です。逆に,関数で変数引数を参照渡しにすることはできません。どうしてもという場合の対応は,引数とする代わりに外部関数定義の場合はモジュールの広域変数を用いるか,内部関数にする,関数をやめて副プログラムに書き換えるかです。

配列引数

Full BASICでは配列を関数や副プログラムの引数にすることができます。
その場合,関数定義や副プログラムの内部で仮引数にした配列を重複してDIM文で宣言してはいけません。
Full BASICの手続き定義は,大きさ(要素数)の異なる配列を引数として受け取ることができます。
引数として受け取った配列の添字の下限と上限はLBOUND関数とUBOUND関数で調べられるので,配列の大きさを別に引数として渡す必要はありません。
関数定義の配列引数は値渡し,副プログラムと絵定義の配列引数は参照渡しになります。
Full BASICでは,配列値を取る関数を定義することができないので,結果として配列が欲しい場合は,副プログラムを利用することになります。
また,関数定義に大きな配列を渡すと配列の複写に時間がかかります。

宣言文

Full BASICの宣言文の有効範囲は,プログラム単位です。
Full BASICの文法は,宣言文を内部手続き内に書くことを許していますが,その場合でも,その宣言文の有効範囲はプログラム単位です。DECLARE文やDIM文を内部手続き内に書いても内部手続きの局所変数の宣言にはなりません。

内部手続きと外部手続き

Full BASICでは,END行より手前を主プログラム, END行以降にEXTERNALを前置して書かれる関数定義,副プログラム,絵定義を外部手続きといい, 主プログラムと外部手続きをプログラム単位といいます。 また,プログラム単位の内側に書かれる関数定義,副プログラム,絵定義を内部手続きといいます。

プログラム単位は変数名や宣言文の有効範囲です。
内部手続きの中では,仮引数以外の変数はプログラム単位で共有されます。 したがって,内部手続きからプログラム単位内の変数を参照し,書き換えることもできますが,内部手続き内での処理にその外側で使っているのと同じ名前の変数を使うと,論理の混乱を起こす可能性があることも意味します。

外部手続きは独立の存在なので,プログラムを記述するとき,他のプログラム単位で用いられている変数名について気を配る必要がありません。 その代わり,OPTION ANGLE,OPTION BASE などの宣言は,プログラム単位ごとに書かなければなりません。 OPTION BASE,OPTION ANGLEはプログラム単位ごとに異なってもかまわないので,忘れずに書く必要があります。

プログラム単位間で,引数によらずに変数を共有したい場合は,モジュールの機能を利用します。モジュールは,静的な変数と外部手続きの集まりです。モジュールでPUBLIC属性で宣言された変数は,モジュール外からも参照できます。 (静的な変数とは,プログラム実行中,常に存在する変数のことです。主プログラムの変数は静的ですが,外部手続き中の変数は,その手続きの実行を終えると消滅します。)

手続きの再帰呼び出し

外部手続きが呼び出されると,参照渡しされた仮引数以外のすべての変数に対して新たなメモリーが割り当てられ,手続きの実行が終わるとそのメモリーは解放されます。
内部手続きの場合は,参照渡し以外の仮引数についてのみ,新たなメモリーに変数が割り当てられ,仮引数以外の変数はそのままです。

この違いは,手続きを再帰的に用いる場合,重要な相違になります。
次のプログラムは,(仮称)十進BASICによる JIS Full BASIC入門 4.2.4 不定方程式の整数解にある,不定方程式a x + b y = c の整数解を探すプログラムです。100行以下の外部副プログラムsolve(a,x,b,y,c)は,a,b,cに具体的な数値を入れて呼び出すと,変数x,yにその解が入って戻ります。

10 DECLARE EXTERNAL SUB solve
20 INPUT a,b,c
30 WHEN EXCEPTION IN
40    CALL solve(a,x,b,y,c)
50    PRINT x,y
60 USE
70    PRINT "解なし"
80 END WHEN
90 END
100 EXTERNAL SUB solve(a,x,b,y,c)
110 IF b=0 THEN
120    IF MOD(c,a)=0 THEN
130       LET x=c/a
140       LET y=0
150    ELSE
160       LET x=1/0           ! 例外を発生させる
170    END IF
180 ELSE
190    LET q=INT(a/b)
200    LET r=MOD(a,b)
210    CALL solve(b,u,r,v,c)
220    LET x=v
230    LET y=u-q*v
240 END IF
250 END SUB 

このプログラムは,方程式 b u + r v = c の解を求めるために,210行のCALL文で副プログラムsolveの再帰呼び出しを行っています。
このプログラムを,そのまま単純に内部副プログラムに書き換えて,

100 INPUT a,b,c
110 WHEN EXCEPTION IN
120    CALL solve(a,x,b,y,c)
130    PRINT x,y
140 USE
150    PRINT "解なし"
160 END WHEN
170 SUB solve(a,x,b,y,c)
180    IF b=0 THEN
190       IF MOD(c,a)=0 THEN
200          LET x=c/a
210          LET y=0
220       ELSE
230          LET x=1/0           ! 例外を発生させる
240       END IF
250    ELSE
260       LET q=INT(a/b)
270       LET r=MOD(a,b)
280       CALL solve(b,u,r,v,c)
290       LET x=v
300       LET y=u-q*v
310    END IF
320 END SUB 
330 END

とすると正しく動作しません。それは,260行で定めたqの値が,280行の副プログラム呼び出しによって書き換えられてしまい,300行でyの値を計算するときには260行で定めた値と異なるものになっているからです。
一方,外部副プログラムを用いるほうが正しく動作するのは,280行の実行中,参照引数以外のすべての変数について新たなメモリーを割り当てて計算が行われるため,手続き呼び出しが終わって戻ったときには,元の値が保存されているからです。(要するに,変数に対する実際の記憶場所が動的に変化するということです。)


参照拡張命令 LOCAL文


戻る