「C言語/関数」の版間の差分

削除された内容 追加された内容
static変数について勘違いしてたので修正。長いのでいったん投稿。
修正中
11 行
=== 関数の定義 ===
ユーザーが自分で定義する関数について、記法の例を表す。
 
<source lang=c>
//例 2つの整数を引数にとりその和を返却する関数
24 ⟶ 25行目:
 
関数の定義の記述は次のようになっている。
 
<source lang=c>
返却値のデータ型 関数名(引数の型と引数名)
30 ⟶ 32行目:
}
</source>
 
関数名とはその関数を他の関数と区別するために付ける名前のことで、
関数名に使えるのは半角英数、「_(下線)」である。
36 ⟶ 39行目:
関数は他の関数の中で呼び出すことができる。
 
また、引数は、省略手着できる場合がある。つまり、
 
<source lang=c>
返却値のデータ型 関数名()
43 ⟶ 47行目:
}
</source>
 
のように関数を記述する場合もある。
 
80 ⟶ 85行目:
:まず、パソコン視点から見れば、たとえポインタやアドレス機能を使って子サイドを呼び出したところで(たとえアドレスを実引数(親サイド関数の引数)として呼び出しところで、ポインタを仮引数(子サイド(呼び出し先)関数の引数)として呼び出したところで、)パソコンは、通常の関数呼び出しで引数を新アドレスにコピー代入するのと同様に、その引数にされたポインタの値やアドレスの値も新アドレスにコピー代入しようとするのである。(けっして、呼び出し方法時のコピー作業が、ポインタ使用時と非使用時で有無が変わるわけではない。)
 
:なので、アドレスが引数にされた関数呼び出しのさいにも、パソコンは新アドレスに親(呼び出し元引数のアドレス値をコピーする。
 
:だが、関数呼び出しのさいに新アドレスに親サイド(呼び出し元)引数のアドレス値をコピーしたところで、けっして、ポインタに格納されている大もとの親サイド(呼び出し元)引数のアドレスの種類が増えるわけではない。
97 ⟶ 102行目:
このC言語の「ローカル変数」システムこそが、C言語の「関数」システムの真骨頂(しんこっちょう)なのです。
 
通常仕様のベーシックでサブルーチンを呼び出しても、けっして、ルーチン内で使用する変数に呼び出し前の変数とは別アドレスは与えられませんし、そもそもサブルーチン呼び出し時に「引数」のコピーをしませんし、そもそもサブルーチン呼び出しに「引数」は不要ですし、そもそも「引数」のような概念じたいベーシックのサブルーチンの仕様には無いのです。
 
ベーシックでのサブルーチンの呼び出しは、単に「GOTO 100」みたいにGOTO文で行指定して指定先にジャンプするかわりに、行番号100にルーチン名を書いておいて、ルーチン名でジャンプ先を指定できるようにしたものに過ぎません。
117 ⟶ 122行目:
「関数」というが、C言語の関数(function)は、数学の関数(function)とは、まったく違う。
 
これが理解時、学習者の混乱の原因になるだろうとして、90年代ごろから、Visual C++などの言語では、C言語の関数とほぼ同じ意味で「メソッド」という用語が使われた。
 
Visual C++やC#などの言語でいう「メソッド」は、上述のC言語の「関数」と、ほぼ同じ機能、もしくは機能拡張したものである。
130 ⟶ 135行目:
 
<source lang=c>
// static変数の使用例(コンパイルできる)
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
164 ⟶ 169行目:
また、初期化がされる(cに0が代入される)のは、最初に呼び出された1回だけである。
 
いったい何が「静か」(static)なのか不明であるが、おそらく、関数の再度の呼び出し時にもアドレス領域変数新規確保初期化しないままでいるという意味で、「静か」なのだろう。
 
 
なお、下記のように書くと、エラーになる
<source lang=c>
// static宣言でエラーになる例1
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
192 ⟶ 198行目:
}
</source>
 
 
このように、static変数は、呼び出し元の関数は、存在を感知できない仕組みである。これによって、バグ発生時に原因箇所を特定しやすくしている。
210 ⟶ 215行目:
 
int main(void) {
int b = 0; // main関数で定義を行った場合
static int c = 0;
 
237 ⟶ 242行目:
 
 
なお、最初の「例1」で紹介した例では、main関数で使用される変数bとcの上にある関数kansuuで定義されてるので、順序的には問題ないのですが、しかしC言語では、アクセスできないようになっています。staticかどうかに関わらず、アクセスできません。kansuuの終了時に、その関数の型などの情報も消えるので、エラー例1のコードでは変数bとcの宣言内容を発見できず、利用できない。
 
 
結局、冒頭に書いたコンパイル成功例のような書き方しか、ありません。
実用では、子サイド(呼び出し先)の関数で、親サイド(呼び出しもと)の変数の値も変更したい場合も多く、その場合には、「 static 変数」(静的変数)もしくは「ポインタ」という機能をつかう。
 
static 変数とは、変数の宣言時に
static int x;
などと static をつけて宣言すると、その名前の変数は、どこの変数で呼び出されても、共通のアドレスで管理しているので、呼び出した(子サイドの)関数の先で、親サイド(呼び出し元の)関数も書き換えできる機能をもつ。
 
いったい何が「静か」(static)なのか不明であるが、おそらく、関数呼び出し時にもアドレス領域を新規確保しないままでいるという意味で、「静か」なのだろう。
 
なお、static宣言した変数を、他の関数とも共有したい場合には、さらに「グローバル変数」という物を使います。
 
すべての関数ブロックの外の、コード前半部の部分(グローバル領域)で、変数宣言をすると、その変数は、すべての関数からアクセスできます。
「static」 (スタティック、静か)という名称からは想像しづらいが、staticで宣言された変数は、その宣言のされた場所の関数を親サイドと認識して、その親関数から呼び出された各関数を子サイドと認識する。そして、子サイド(呼び出し先)の各関数ブロック内と、親サイドで、その変数が「共有」され、さらに、子(呼び出し先)の各関数ブロックによる親サイドの変数値の「書き換えが可能」なのである。変数のアドレスが共有された結果、書き込み場所が1つ(その書き込み先の場所は、宣言した関数ブロックにある)しかないので、自動的に、呼び出し先の各関数ブロックで計算した内容が、宣言場所の変数にも代入されてしまうわけである。
 
子サイドの関数が2個以上の複数ある場合、それぞれの子が親と共有するので、結果的に、子関数どうしでも変数を共有することになる。
 
 
実用では、子サイド(呼び出し先)の関数で、親サイド(呼び出しもと)の変数の値も変更したい場合も多く、その場合には、「グローバル変数としての static 変数」(静的変数)もしくは「ポインタ」という機能をつかう。
もし、「親子共有変数」とか「子関数からでも書き換え可能な変数」とか、そういう名前がついていたら分かりやすいが、残念ながら歴史的な理由により、「静的変数」という名前がついてしまった。
 
などと グローバル領域でのstatic をつけて宣言すると変数は、その名前の変数は、どこの変数で呼び出されても、かつ共通のアドレスで管理しているので、呼び出した(子サイドの)関数の先で、親サイド(呼び出し元の)関数も書き換えできる機能をもつ。
 
 
さて、C言語の入門書での「関数」の章の前半に書いてあるプログラムで、static変数やグローバル変数やポインタを使わなくても利用できる(ユーザー定義)関数は、むしろ珍しいプログラムなのである。画面に「こんにちは!」などの文字などを表示するだけなら、ポインタstatic変数などを使わなくても、そのようなアルゴリズムを構築できるので、そのようなプログラムがC言語の入門書の『関数』の章に書かれるのであろう。
 
なお、「return a;」などのように戻り値をつかって 計算結果の数値を 親(呼び出しもと)の関数に送信したりする方法は、じつは、あまり実用的ではなく、現実には不便な場合が多いのである。
366 ⟶ 370行目:
通常のローカル変数は、関数ブロックが終了するときに、変数の結果が消去される。そして、再度関数を呼び出したときは、変数の宣言時に変数が作成されたり、あるいは関数ブロックに入るときに作成され、そしてまた、その関数ブロックが終了するときに、消去される。
 
この消去により、メモリ領域ある関数が別の関数の変数圧迫し書き換えないような仕組みになっている。しかし、このような関数では、もし、ある関数を複数回呼び出しても、前回使用した計算結果が消去されてしまっており、そのため、前回の計算結果を再利用するような作業が困難である。
 
たとえば、高校数学で習うような数列の漸化式のような計算ですら、困難になってしまう。(数列の項を、関数に見立てている。)
416 ⟶ 420行目:
</pre>
 
なので、他の関数と計算結果を共有する必要のない変数であれば、なるべく、個別の関数の内部で変数宣言をするのが良い。
よく、↑こう言われてるが、かといって main関数 で変数を宣言しても、今度は、main関数が他のどの関数ブロックからでもアクセスできるので、やはりバグの発見が困難になる。
 
 
もし「main関数から関数Aを呼び出して、関数Aからは関数Bと関数Cを呼び出す仕様。関数Bで計算した結果の変数を、関数Cでも利用する。」などのコードが3階層(mainの階層、Aの階層、BとCの階層)に分かれた仕様のプログラムなら、変数を共有したい子関数Bと子関数Cの共通の親(呼び出し元)である関数Aで変数宣言をしてstatic宣言をするのが、バグ発見をしやすいプログラムになるだろう。
 
 
要するに、上述の例のようにプログラムの関数が幾層にも分かれているとき、変数を共有したい複数の関数ブロックがあれば、その手前の段階の階層で、宣言をすると、バグ発見がしやすくなる。
 
いっぽう、他の関数と計算結果を共通したい場合、どうしてもグローバル変数を使わざるを得ない場合も多い。(もし、グローバル変数を用いないと、代わりにポインタを使わざるを得ない場合がある。)
初心者の段階では、幾層もあるプログラムは書かないので、あまり着にする必要が無い。
 
これはつまり、そもそも、複数の関数どうしでの変数の共有は、なるべく、ひかえる必要がある、という事である。
また、main関数しかないプログラムなら(関数が「1階層」しかないなら)、グローバル変数を使わずにmain関数内ですべて変数宣言をする行為は、もしも、宣言したい変数の個数が多い場合には(たとえば30個の変数を宣言する場合を考えてみればいい)、単にmain関数のプログラム内容を見づらくするだけであり、「グローバル変数を使わないようにしよう」という努力があまり役立たない。