C言語の解説と演習(第十回)


内容

  1. はじめに
  2. 関数のその次の一歩(2)
  3. 演習問題

1. はじめに

今回は前回のプログラムをどのように修正すれば、うまく動くようになるのかを 解説します.ポインタを使いますので、 まだポインタのところを良く理解していない人は 「C言語の解説と演習(第六回)」を見なおしてください.


2. 関数のその次の一歩(2)

前回は正しく動作しないプログラムとして次のプログラムを紹介しました.

#include <stdio.h>

main()
{

  int x,y,wa,sa,seki,kekka;
  int tashizan(int x, int y, int kekka);
  int hikizan(int x, int y, int kekka);
  int kakezan(int x, int y, int kekka);

  printf("x=");
  scanf("%d",&x);
  printf("y=");
  scanf("%d",&y);

  tashizan(x,y,kekka);
  wa = kekka;
  hikizan(x,y,kekka);
  sa = kekka;
  kakezan(x,y,kekka);
  seki = kekka;

  printf("x+y=%d¥n",wa);
  printf("x-y=%d¥n",sa);
  printf("x*y=%d¥n",seki);

}

int tashizan(int x, int y, int kekka) {

  kekka = x+y;
  return kekka;
}

int hikizan(int x, int y, int kekka) {

  kekka = x-y;
  return kekka;
}

int kakezan(int x, int y, int kekka) {

  kekka = x*y;
  return kekka;
}

前回述べたように、C言語にはローカル変数とグローバル変数があり、関数を呼び出すと、新しくローカル変数の領域が取られます.そのため、上の関数 tashizan, hikizan, kakezan の中で変数 kekka に数値を代入しても、関数 main の変数 kekka は変わりません.だから、上のプログラムは思ったように動作しないのでしたね.
それではこのプログラムを正しく動作させるためには、どうしたら良いのでしょうか?「演習問題の解答」に示したように、これには2つのやり方があります.
1つは「全ての変数をグローバル変数にし、ローカル変数は使わない」という方法です.全ての変数をグローバル変数として宣言すれば、グローバル変数は全ての関数から使えますから、関数 tashizan, hikizan, kakezan を呼び出す時に、変数 x, y, kekka を与える必要はありませんし、関数の内部でローカル変数を宣言する必要もありません.「演習問題の解答」に示したように、プログラムは次のようになります.

#include <stdio.h>

int x,y,wa,sa,seki,kekka;

main()
{

  int tashizan();
  int hikizan();
  int kakezan();

  printf("x=");
  scanf("%d",&x);
  printf("y=");
  scanf("%d",&y);

  tashizan();
  wa = kekka;
  hikizan();
  sa = kekka;
  kakezan();
  seki = kekka;

  printf("x+y=%d¥n",wa);
  printf("x-y=%d¥n",sa);
  printf("x*y=%d¥n",seki);

}

int tashizan() {

  kekka = x+y;
  return kekka;
}

int hikizan() {

  kekka = x-y;
  return kekka;
}

int kakezan() {

  kekka = x*y;
  return kekka;
}

上のプログラムでは、全ての変数 x, y, wa, sa, seki, kekka をグローバル変数として宣言しています.よってこれらの変数はどの関数からでも使えるようになっています.ですから、関数 tashizan, hikizan, kakezan には入力の変数 x, y, kekka を与える必要がありません.BASIC のプログラムのサブルーチンと全く同じです.BASIC のプログラムをC言語のプログラムに書き直すときは、上のように、全ての変数をグローバル変数として宣言したほうが早いでしょう.
「じゃあ、始めから上のように全ての変数をグローバル変数として宣言して、C言語のプログラムを書けば良い」と思うかもしれませんが、実はそうではないのです.C言語ではグローバル変数はなるべく使わないほうが良いといわれています.確かに全ての変数をグローバル変数として宣言すれば、どこでもその変数が使えるので便利です.しかし言葉を変えれば、それはどこでもその変数の値が変えられてしまう可能性があるということです.例えば、上のプログラムの関数 kakezan が

int kakezan() {

  wa = x;
  kekka = x*y;
  return kekka;
}
となった場合を考えてください.関数 kakezan を呼び出すと変数 wa の値は変数 x の値になってしまい、最後に x+y = x というおかしな計算結果が表示されてしまいます.こんな馬鹿なことは普通しないと思うかもしれませんが、そんなことはありません.上のプログラムは短いので、全体をすぐ見渡すことができますが、実際に社会で使われているプログラムは少なくとも何千行、時には何万行にも渡るようなプログラムがほとんどです.これほど大きなプログラムになると、全く違った意味で使っている変数の名前がたまたま同じになってしまったということは大いにありえることです.そして不幸にもそう言ったことが起こってしまった場合には、どこが間違っているかを探す作業(これを一般にデバッグと呼びます)は困難を極めます.そこでC言語では、関数の内部でのみ有効なローカル変数を定義し、関数の内部での計算が他に影響を与えないようにしているのです.先ほどの関数 kakezan も

int kakezan(int x, int y, int kekka) {

  int wa;

  wa = x;
  kekka = x*y;
  return kekka;
}
と変数 wa がローカル変数として定義されていれば、関数 main の変数 wa は全く変わらないのは前に説明した通りです.このように、

(1)なるべくローカル変数を用いて、関数の内部での計算が他に影響を与えないようにする.
(2)関数の入力と出力は明示的に与える.

ことを関数のモジュール化といいます.BASIC (N88BASIC のことです.最近の BASIC に関しては知りません)にはローカル変数がないので、このように関数のモジュール化を行うことができません.よって BASIC で大規模なプログラムを作ることは困難です.この理由から、実際の現場ではあまり BASIC は使われていないようです.

それでは、ローカル変数の重要性を強調したところで、グローバル変数を用いない修正法について説明します。これにはポインタを使います。プログラムは 「演習問題の解答」 に示したように 次のようになります。

#include <stdio.h>

main()
{

  int x,y,wa,sa,seki,kekka;
  int tashizan(int x, int y, int *kekka);
  int hikizan(int x, int y, int *kekka);
  int kakezan(int x, int y, int *kekka);

  printf("x=");
  scanf("%d",&x);
  printf("y=");
  scanf("%d",&y);

  tashizan(x,y,&kekka);
  wa = kekka;
  hikizan(x,y,&kekka);
  sa = kekka;
  kakezan(x,y,&kekka);
  seki = kekka;

  printf("x+y=%d¥n",wa);
  printf("x-y=%d¥n",sa);
  printf("x*y=%d¥n",seki);

}

int tashizan(int x, int y, int *kekka) {

  *kekka = x+y;
  return *kekka;
}

int hikizan(int x, int y, int *kekka) {

  *kekka = x-y;
  return *kekka;
}

int kakezan(int x, int y, int *kekka) {

  *kekka = x*y;
  return *kekka;
}

修正前のプログラムとの違いは、関数 tashizan, hikizan, kakezan の3番目の入力 kekka の変数の型が、整数から整数のポインタ int *kekka へと変わっていることですね。関数の定義も、それに応じて変わっています。それでは、プログラムの解説をしていきましょう。プログラムを動かすとまず、変数 x, y にキーボードから入力した数値が代入されるのは、前に説明した通りです。今、キーボードからの入力が、x = 4, y = 6 だとすると、この時のメモリの状態は次のようになっています。




関数 tashizan が

  tashizan(x,y,&kekka);

のように呼び出されると、&kekka は変数 kekka のアドレス 105番地を意味しますから、新しく作られる関数 tashizan のローカル変数の領域は次のようになります。




ここで、関数 tashizan の内部の計算

  *kekka = x+y;
が行われると、関数 tashizan の変数 kekka は 105 番地を指しているアドレスですから、*kekka = x+y は x+y の値を 105 番地に代入しろということです。よってメモリの状態は次のようになります。




そして、

  return *kekka;
で、105番地の値、すなわち 10 を関数の値として、関数 main に返します。関数 main は

  tashizan(x,y,&kekka);
  wa = kekka;
となっていますから、関数 tashizan の返した値 10 は使われずに、変数 kekka(105番地) の値 10 が変数 wa に代入されますから、メモリの状態は次のようになります。




よって x+y=10 が正しく、変数 wa に代入されました。関数 hikizan, kakezan も同様にして、正しく実行され、変数 sa, seki にそれぞれ -2, 24 が代入されます。
以上でポインタを用いた修正プログラムの解説を終わりますが、最後にもう一言。
以前に、キーボードからの入力を行う関数 scanf() で

  scanf("%d",x);
でなく、

  scanf("%d",&x);
と変数 x の前に & をつける必要があるが、これについては後で説明すると言いましたね。今はもう何故 & をつける必要があるかわかるはずです。& をつけないと、今回の関数 tashizan, hikizan, kakezan の変数 kekka と同じようにローカル変数の値を変えるだけで、呼び出した側の変数の値は変わらないからですね。

それでは今回のまとめを行います.今回もいろいろと新しいことが出てきました.
  1. 先週の問題に対しては、グローバル変数を用いたものとポインタを用いたものの2つの解決法がある。
  2. BASIC (N88BASIC) では、すべての変数がグローバル変数である。よって BASIC のプログラムを C 言語のプログラムに変換するときは、すべての変数をグローバル変数にすればよい。
  3. 一般的に、C言語ではグローバル変数はなるべく使わないで、ローカル変数を用いた方が良いとされる。

  4. 関数の内部では、ローカル変数のみを使い、関数の内部での計算がほかに影響を与えないようにし、入力と出力を明示することを「関数のモジュール化」という。
  5. ポインタを使うと、「関数のモジュール化」を行ったままで、先週の問題を解決できる。この理由で、入力を行う関数 scanf でもキーボードからの入力を代入する変数には & をつけ、ポインタにする必要がある。

3. 演習問題

「BASICプログラミング」の「サブルーチンを使って多項式の値と微分値を求める」にある BASIC のプログラムをC言語に書き直せ。ポインタを用いた関数のモジュール化を行え。

答えを見る