2017.02.09
Jon Paris 著

パラメーターの受け渡し

たとえ、高級言語によって「裏で」何が起きているのか理解することが不要になっても(多くの場合、理解することは難しいが・・)、私は常々、内部処理の基本知識は非常に貴重であると思っていました。これは特に、謎の多いバグを解決する場合に当てはまります。最近、インターネットのリスト、また顧客から直接送信された電子メールで見つけたパラメーターの受け渡しに関する多くの問題から、このことを思い出しました。 私の内部処理を理解する必要性は、少なくとも一部は、自分のキャリアの早期段階でアセンブラーと他の低水準言語を学習したという事実によるものです。実際に、私が一番初めに学習したことは、プラグ・ボードを使用して計算機およびタブをプログラムする方法でした。これらを使用して、何が起きていたのかメカニズムを理解する必要がありました。

すでに理解している読者には申し訳ありませんが、パラメーターの受け渡しについて最初からお話ししようと思います。
覚えておく最も重要な点は、あるプログラムから別のプログラムへパラメーターを受け渡す場合、データは一切受け渡していないということです。渡しているのは、起点プログラムにあるパラメーターのストレージの先頭バイト (のメモリー・アドレスなど) へのポインターです。渡しているのはそれがすべてです。 この点を理解することは重要です。つまり、データ型およびデータ長の点から、そのパラメーターがどのように見えるかを効果的に定義する受信プログラムであり、注意しないとそれが「興味深い」問題を引き起こす可能性があるからです。私の言っていることを確認するため、簡単な例を見てみましょう。

技術情報code01

上記のコードで、(A) に、呼び出そうとしているプログラムである CALLTGT1 のプロトタイプがある点に注目してください。このプログラムは第 2 パラメーターの内容を変更します。第 2 パラメーターは 10 文字の変数として定義されている点に注意してください。

(B) に、変数 parmData10 の定義があります。この変数は、(D)にその第 2 パラメーターとして渡されます 。呼び出す前に、内容を確認できるよう parmData10 変数および moreData 変数の両方の内容を表示します。

(D) の呼び出しに続いて、再度 2 つの変数の内容を表示します。プログラムの実行結果が以下のとおりです。

技術情報code02

予想通り、parmData10 の内容が変更されています。しかし、変数 moreData の内容も変更されている点に注目してください。それはどのようにして起きたのでしょうか? 呼び出されているプログラムのソースを見れば、問題がわかると思います。以下のようになります。

技術情報code03

問題がわかりますか?そうです!呼び出し側プログラムで 10 文字で定義されたパラメーターが、受信側プログラムでは 20 文字で定義されています。したがって、4 文字の &input 変数が &output 変数に移動する場合、6 つのスペースが埋め込み文字として追加されるのではなく、プログラマーの予想どおり合計 16 文字が追加され、その結果、変数 moreData の最初の 10 文字が上書きされています。moreData は不幸なことにメモリーで parmData10 に続いているためです。
悲しいことに、こうした破損に気付くのはそれほど簡単ではありません。この点を示すためだけに、呼び出し側プログラムにこうした簡単な変更をしてみてください。data データ構造 (DS) で変数 moreData と parmData10 の順序を逆にします。DS は以下のようになります。

技術情報code04

これでどうなると思いますか?この場合、プログラムは正常に動作し、結果は以下のようになります。

技術情報code05

なかなか良いでしょう?破損は見られません。ただし、破損はまだ発生するに違いないということはわかっています。では、それら余分な 10 個のスペースはどこへ行ったのでしょうか?あなたと私の見当は当たっています。現在上書きされている正確なデータ内容について見当がつかないというのが実際のところです。RPG コンパイラーはメモリーに変数を配置します。我々の観点からは、完全に任意のシーケンスになっています。元の例では、変数を DS に故意に配置しました。それが、メモリーに変数が互いに配置されていることをきちんと保証でき、そのため破損が発生していることを観察できる唯一のタイミングであるからです。しかしそれでも、DS の最後に何が続くのか知る術はありません。
そうしたエラーの結果を診断するのは本当に難しいので、この点を理解することが重要です。破損した変数が更新操作により引き続きデータベースに書き戻され、それが数週間後に見つかり、それらの変数の一部であるヒストリー・ファイルを、完全にビルドし直さなければならないというケースを見たことがあります。
別のケースでは、元々印刷バッファーが破損していましたが、印刷行は破損が発生した後にアセンブルされたため、プログラムは正しく動作したようです。そして、何年にもわたって動作し続けています。そしてある日、再コンパイルしなければなりませんでした。プログラマーが行った変更はささいなもので、メモリー・レイアウトに影響ありませんでしたが、しばらくの間、コンパイラー・フォークにより、可変ストレージが生成されるやり方が変わっていました。結果として突然、重要な内部プログラム制御ポインターが破損し、数分後プログラムが「ドーンと」実行されました。
ではこうしたエラーをどのように回避すればよいでしょうか?プロトタイプを使用する、もっと正確に言えば、正確なプロトタイプを使用するというのが答えです。ここで示したコードは、客先でよく目にする典型的な例です。ラベル (A) の付いたプロトタイプは、そのプロトタイプを使用するプログラムを作成したプログラマーによって作成されたようです。ヒントは、プロトタイプが実際は /COPY ディレクティブ経由で出現している一方、ソースではハードコーディングされていたという事実にあります。RPG ソースでハードコーディングされたプロトタイプを確認した場合、疑ってください。非常に疑って、できるだけ早く取り除いてください。
その /COPY ソースは、呼び出し中の CL ルーチンをコーディングしたプログラマーによって作成されていたはずです。CL プログラムを呼び出す場合、実際には、プロトタイプが被呼プログラムにより使用された実際のパラメーターと一致するよう、手動で確認する以外の選択肢はありません。C 関数およびシステム API については、IBM 提供のプロトタイプがありますが、私のように、それらのスタイルや命名規則が気に入らない場合、軽くインターネット検索すると、誰かが作成した素晴らしい例が見つかる場合が少なくありません。
プロトタイプが RPG プログラムに関連している場合、状況は好転しています。被呼プログラムおよびすべての呼び出し側プログラムで /COPY をコーディングすることで、プロトタイプがインターフェースの有効な表記であることを確認できます。両方が存在する場合、コンパイラーはプロトタイプ (PR) とプロシージャー・インターフェース (PI) を比較し、一致しないとコンパイルが失敗します。プロトタイプに対するコンパイラーの規則が最近緩いのは嬉しいのですが (内部サブプロシージャーのプロトタイプをコーディングする必要がなくなったなど)、例えば、PI が定義されているプログラムでは、プロトタイプが必要なくなるという残念な副作用があります。その結果、プロトタイプで /COPY しなくても済みますが、誘惑に負けないでください。コンパイラーに使用する予定のプロトタイプを検証させれば、やがて問題を回避できるでしょう。

ページトップ

ボタン