31.10 プログラム実行の処理

ところで,コンピュータはプログラムをどのように動かしているのでしょうか.
Java では “System.out.println” などのメソッドを使わない限り計算結果を表示しないので,中でどのような計算をしているのかはブラックボックスのような状態です.しかし,コンピュータの処理は細かく見ると CPU で加減乗除といった演算と条件分岐などの制御を繰り返し行っているだけなので,何項もある数式をいっぺんに処理するわけではありません.ましてやプログラムを書く人が自由に作れる関数などどう扱っているのでしょうか?
そこでここでは,コンピュータがプログラムを処理する手順を模式的に説明します.鍵となるのは計算の実行と関数の呼び出し,式の置き換えです.

プログラムの実行のされ方

例えば,

public static double square(double x) {
  return x * x;
}
public static double discriminant(double a, double b, double c) {
  return square(b) - 4 * a * c;
}

というメソッドが定義されていたときに,

System.out.println(discriminant(1, -5, 6));

という処理をどのように行っているのかを見てみましょう.

  1. “System.out.println(discriminant(1, -5, 6));” という処理のためには,まず,
  2. “discriminant(1, -5, 6)” が計算され,これはメソッド “discriminant” の処理 “return square(b) – 4 * a * c;” を行うことになります.
  3. ただし,“a”, “b”, “c” という引数にはそれぞれ“1.0”, “-5.0”, “6.0” という値が渡されて,次の処理になります.
  4. “return square(-5.0) – 4 * 1.0 * 6.0;” で,次の式を計算します.
  5. “square(-5.0) – 4 * 1.0 * 6.0” で,この式は引き算なので,“-” の左辺と右辺がそれぞれ計算されます.
  6. 左辺の“square(-5.0)” はメソッド “square” を呼び出して,“return -5.0 * -5.0” になります.
  7. “-5.0 * -5.0” はかけ算をして,値は “25.0” です.
  8. 一方,引き算の右辺 “4 * 1.0 * 6.0” はかけ算により(正確には “4 * 1.0” をして “4.0” と求めてから “4.0 * 6.0” を計算することで),“24.0” となります.
  9. よって,“square(-5.0) -. 4.0 *. 1.0 *. 6.0” は “25.0 – 24.0” になり,
  10. “25.0 –. 24.0” は “1.0” なので,
  11. 最初の “System.out.println(discriminant(1, -5, 6));”は“System.out.println(1.0);” となります.
  12. 最後に “System.out.println” が呼び出されて,その処理により “1.0” が画面に表示されます.

どうでしょうか?人間が数式を,定義に立ち返りながら,一歩ずつ順番に計算するのと似ていると思います.

計算規則のまとめ

プログラムの実行のされ方を抽象的にまとめると,次のようになります:

  • 整数や小数,真偽値といった最小の要素を値と言います.
  • 変数は値を格納する名前付きの箱のようなものですが,参照されるとその値になるので値と見なします.
  • 値に “+” などの演算子,メソッドなどを組み合わせたものを式と言います.
  • また,変数の定義・代入,メソッドの呼び出し,“return”, “if”, “for” などを処理と言います.
  • さらに処理をいくつか並べてメソッドが定義できます.
  • 特に “main” として定義したメソッドは特殊なものと見なされて,そこからプログラムの処理が始まります.
  • 式は以下の手順を使い簡単にしていくと値になります.
  • 式が “式1 演算子 式2” の形になっている時,
    1. “式1′ と “式2” を計算して,それぞれの値で置き換えます.
    2. “値1 演算子 値2” の形になったところで,演算子の計算規則から値を求めます.
    3. これが “式1 演算子 式2” の形の式の値です.
  • 式が “メソッド(式1, 式2, …)” の形になっている時,
    1. “式1”,“式2”,…を計算して,それぞれの値で置き換えます.
    2. “メソッド(値1, 値2, …)’ ‘の形になったところで,メソッドが呼び出されます.
    3. つまり “戻り値の種類 メソッド(引数1の種類 引数1, 引数2の種類 引数2, …) { 処理; }” という定義がされていた場合,“処理” の中に現れる “引数1”,“引数2”,…はそれぞれ “値1”,“値2”,…で置き換えられます.
    4. こうして値の代入が行われた “処理” を順番に行って,“return” にたどり着いたところでその値が “メソッド(式1, 式2, …)” の形の式の値になります.

練習問題として,“number_of_roots(1.0, 5.0, 6.0)” という式がどのような手順で計算されているのか,紙に書いてみましょう.

デバッグの方法

今までは計算結果しか画面に表示させませんでしたが,プログラムの作成段階,特にプログラムは実行できるのになぜか変な結果になる時は,“System.out.println” を書き足して途中計算が正しくできているかきちんと確認するとよいです.書き足す場所や何を表示させるか決めるにはコツがいりますが,条件分岐や繰り返しの確認,メソッドに渡される値とその計算結果は押さえておくとよいです.
以下にメソッドがうまく使えているかをチェックする例を示します.ここで “System.out.println(“square(” + x + “)”);” において “square(”, “)” は文字列で “+” によりつなげることができます(“x” のような値は文字列に変換されます).また,計算結果は “return” の前に表示しなければならないので,“result” という変数を作りいったんそこに計算結果を入れています.

// Program.java: 判別式 デバッグ class Program { public static double square(double x) { System.out.println("square(" + x + ")"); double result = x * x; System.out.println("square(" + x + ") = " + result); return result; //return x * x; } public static double discriminant(double a, double b, double c) { System.out.println("discriminant(" + a + ", " + b + ", " + c + ")"); double result = square(b) - 4 * a * c; System.out.println("discriminant(" + a + ", " + b + ", " + c + ") = " + result); return result; //return square(b) - 4 * a * c; } public static void main(String[] args) { System.out.println(discriminant(1, -5, 6)); } }

discriminant(1.0, -5.0, 6.0) square(-5.0) square(-5.0) = 25.0 discriminant(1.0, -5.0, 6.0) = 1.0 1.0

この方法は原始的ですが,プログラムの誤作動の原因究明の重要な手がかりになります.