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


内容

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

1. はじめに

前回述べたように、Cにおける関数では「関数への入力」と「関数の出力 (関数の返す値)」をはっきりと指定しなければなりません. 今回はこの入力と出力の取り扱いについて、特にBASICとの違いに焦点を当て、 解説していきます.まだ前回を解説を理解していない人は、 まずそれをしっかり理解してから今回の解説を読むようにしてください. 今回の内容は重要なので2回に分けて解説を行います.今日はその一回目です.


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

前回は例題として、キーボードから入力された2つの数 x,y の和、差、積を計算し、表示する次のプログラムを取り上げました.

#include <stdio.h>

main()
{

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

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

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

  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;
}


このプログラムでは、次の関数

  int tashizan(int x, int y);
  int hikizan(int x, int y);
  int kakezan(int x, int y);

を使っていますね.関数 tashizan は

  int tashizan(int x, int y);

と宣言されていますから、 int x, int y (整数x, y) を入力とし、整数を返す関数ですね. 実際に関数の定義を見てみると、

int tashizan(int x, int y) {

  int kekka;

  kekka = x+y;
  return kekka;
}
となっており、整数 x, y を入力として、x+y を返す関数となっています.関数 hikizan,kakezan も同様です.

さて、それでは今度は関数 tashizan, hikizan, kakezan を

  int tashizan(int x, int y, int kekka);
  int hikizan(int x, int y, int kekka);
  int kakezan(int x, int y, int kekka);

と宣言し、int x, int y (整数 x, y) を入力とし、計算結果を変数 kekka に代入するようにプログラムを書きなおしてみましょう.プログラムは次のようになります.

#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;
}

前回のプログラムとの違いは、

(1) int 型の変数 kekka の変数宣言が加わったこと、

(2) 関数宣言が

  int tashizan(int x, int y, int kekka);
  int hikizan(int x, int y, int kekka);
  int kakezan(int x, int y, int kekka);
となっているところ

(3) 関数定義が

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;
}


となったところ
(4) 関数の呼び出し部分が

  tashizan(x,y,kekka);
  wa = kekka;
  hikizan(x,y,kekka);
  sa = kekka;
  kakezan(x,y,kekka);
  seki = kekka;
となったことですね.

ではプログラムの流れを見て見ましょう.前回説明したように、プログラムが実行されると、変数 x, y にキーボードから入力された2つの数値が代入されます.次に関数 tashizan の関数呼び出し、

  tashizan(x,y,kekka);
が実行されますが、呼び出された関数 tashizan では

  kekka = x+y;
  return kekka;

が実行されますから、まず、x+y が変数 kekka に代入され、その値が関数の値として返されます.関数の呼び出し側では

  tashizan(x,y,kekka);
  wa = kekka;
となっていますから、関数の値は使われません.その代わりに変数 kekka の値が変数 wa に代入されます.変数 kekka には x+y の値が代入されていますから(関数 tashizan の中で)、これで変数 wa には x+y の値が代入されたことになります.関数 hikizan, kakezan の動作も全く同じです.そして最後に printf で計算結果が表示されます.

ここまでは、プログラムの内容を理解できましたか?
実はこのプログラムは正しく動作しません.プログラムの文法上の間違いはないので、プログラムの実行ができますが、おそらく最後の printf ではx+y, x-y, x*y として全くでたらめな数が表示されるでしょう.こうやってわざわざ間違ったプログラムを説明したのは、この点がBASIC のプログラムに慣れた人がよく間違えるところだからです.
それではどこが間違っているか、解説していきましょう.まず、C言語のローカル変数とグローバル変数の違いについて説明します.C言語で宣言される変数は、ローカル変数とグローバル変数の2つに分けられます.ローカル変数は関数の内部で宣言される変数です.このローカル変数は宣言された関数の内部でのみ有効です.例えば、先ほどのプログラムの変数宣言

  int x,y,wa,sa,seki,kekka;
では、関数 main の内部で変数が宣言されているので、これらの変数はローカル変数であり、関数 main の内部でのみ有効です.ですから、例えば関数 tashizan, hikizan, kakezan の内部でこれらの変数を使うことはできません.これに比べ、グローバル変数は関数の外で宣言される変数で、全ての関数から使うことが可能です.先ほどのプログラムではグローバル変数は宣言されていません.例えば




のように変数が宣言されていたら、変数 x, y はグローバル変数、変数 x1,y1 は 関数 mainの内部だけで有効なローカル変数、x2,y2 と x3,y3 もそれぞれ、関数 tashizan と hikizan の内部だけで有効なローカル変数です.ちなみに BASIC では全ての変数がグローバル変数なので、このような区別を考える必要はありませんでした.

先ほどのプログラムでは、変数 x,y,wa,sa,seki,kekka は関数 main の内部のみで有効なローカル変数なので、関数 tashizan, hikizan, kakezan からは使うことができません.そこで、関数 tashizan を呼び出すときには

  tashizan(x,y,kekka);
の形で呼び出し、ローカル変数x, y, kekka の値を渡していたのでした.
実は関数 tashizan を上の形で呼び出すと、メモリ上に新しく関数 tashizan のローカル変数の領域が取られ、関数 tashizan の変数 x,y,kekka がその領域に割り振られます.そして、関数 main のローカル変数 x,y,kekka の値がそこへコピーされた後に、関数 tashizan の中身が実行されます.読んだだけでは分かりにくいと思うので、図で示します.
今、関数 main のローカル変数 x,y,kekka の値がそれぞれ、4,6,8 だとします.このとき、関数 tashizan(x,y,kekka) が呼ばれると、下図のように新しく関数 tashizan の変数の領域がメモリ上に取られ、関数 main のローカル変数 x,y,kekka の値がそれぞれ、関数 tashizan の領域にある変数 x,y, kekka にコピーされます.



上の図から分かるように、関数 main のローカル変数 x,y,kekka と関数 tashizan の変数 x,y,kekka の番地は異っています.つまり全くの別物です.
次に関数 tashizan の中身

  kekka = x+y;
  return kekka;
が実行されると、上の 変数 x,y,kekka は関数 tashizan の変数の領域にある x,y,kekka ですから、メモリの状態は次のようになります.




そして、関数 tashizan は変数 kekka の値 10 を、その値として返るわけですが、関数 tashizan を呼び出した関数 main では

  tashizan(x,y,kekka);
  wa = kekka;
となってますから、関数 tashizan の返す値 10 は使われません.関数 main では、変数 x, y, kekka は関数 main のローカル変数の領域にある変数のことですから、

  wa = kekka;
が実行されると、関数 main のローカル変数 kekka の値 8 が変数 wa に代入され、メモリの状態は




となり、4+6=10 が代入されるべき変数 wa に 8 が代入されてしまいます.以下、関数 hikizan, kakezan の呼び出しの時も全く同じ事が起こり、その結果、変数 sa, seki にも 8 が代入されます.
このように、このプログラムでは正しく動作せずに、全く意図していない結果を返してしまいます.それでは、このプログラムを動作させるにはどうしたら良いでしょうか?これには2つの方法が考えられますが、それは次回に説明することにします.
今回のまとめを行います.今回もいろいろと新しいことが出てきました.
  1. C言語では、変数はローカル変数とグローバル変数の2種類がある.
  2. ローカル変数は関数の内部で宣言された変数であり、その関数の内部でのみ有効である.他の関数からのその変数は使えない.

  3. グローバル変数は関数の外部で宣言される変数で、全ての関数から使うことができる.BASIC では全ての変数がグローバル変数である.
  4. 関数呼び出し f(x,y,...,z) を行うと、関数 f の変数の領域が新しく作られて、呼び出した側の変数 x,y,...,z の値が呼び出された側の変数のところへコピーされる.このとき、呼び出した側の変数 x,y,...,z と呼び出された側の変数 x,y,...,z の番地は異なっており、全く別の変数である.

3. 演習問題

今回のプログラムを、正しく動作させるにはどうしたら良いか考えよ(ヒント:グローバル変数を使うか、ポインタを用いる)

答えを見る