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

削除された内容 追加された内容
編集の要約なし
「呼び出し先」を「子(呼び出し先)」のように記述変更し、親子関係に例える。「呼び出し先」と「呼び出し元」だけだと、どっちがどっちか、まぎらわしいので。
21 行
以下、特にことわらないかぎり、ユーザーが定義した関数を、単に「関数」と省略する。main関数やprintfなども「関数」と省略する場合があるので、読者は文脈から判断のこと。
 
関数を呼び出す際、関数の 呼び出し元(仮に「親」と呼ぼう) から 呼び出し先関数(仮に「子」と呼ぼう)へ情報を渡すことができ、この渡される情報のことを'''引数'''(ひきすう、argument)と呼ぶ。
 
関数の定義の記述は次のようになっている。
35 行
多バイト文字を使用できるかはコンパイラによる。
関数は他の関数の中で呼び出すことができる。
 
また、引数は、省略手着る場合がある。つまり、
<source lang=c>
返却値のデータ型 関数名()
{
/*いくつかの文*/
}
</source>
のように関数を記述する場合もある。
 
 
40 ⟶ 49行目:
{{コラム|局所変数とアドレス操作の関係|
* 局所変数の実現方法
このようなユーザー定義の関数で、呼び出し先関数での引数(上記の例ではint aとint b)の使用のとき、C言語では「子」(呼び出し先関数の使用開始のたびに、引数のために新しいアドレスを用意して、その引数に新アドレスを与えるするという仕組みである。
 
例えるなら、図書館とかで世界に数冊しかないような貴重書を読みたい場合、安全のため図書館は貸し出しさせてくれず、そのかわりコピー機で必要最低限のページだけをコピーさせてくれる場合があるでしょう。関数の引数も、図書館のああいうのと似たような感じで、じつはコピーなのです。
 
関数の引数(ひきすう)は、じつは原則的には、すべてコピーなのです。(例外は、static変数を使う場合や、ポインタ操作をする場合。)
 
なぜなら、C言語において、「局所変数」(「ローカル変数」)という安全実現のための機能を実現するためである。
 
「局所変数」(きょくしょ へんすう)とは、ある関数を呼び出したとき、呼び出された子サイド(呼び出し先で、普通の方法で変数を変更しても、本体である親サイド(呼び出し元の内容には変化を与えないという機能である。
 
C言語は局所変数の機能を実現するために、子サイドの関数を呼びだした時には、パソコンはまず、新しいアドレス内をコンパイラに与えており、それから親(呼び出し元の引数(「実引数」という)のaの値とbの値とをそれぞれ新アドレスに代入するという方法により、子サイドの関数が呼び出された時にaやbの値をコピーしているのである。
 
このため、子(呼び出し先の関数内(仮に関数fとする)で、通常の方法でaやbの値を変更しようが、親(呼び出し元の関数(仮にgとする)で使われている同名の引数aとbのアドレスとは、親(呼び出し先のアドレスとは別のアドレスなので、よって、子(呼び出し先は、親(呼び出し元の内容には、いっさい変化を与えないようにする事ができる。
 
このようにして、C言語において、局所変数(「ローカル変数」)の機能が実現される。
 
このため、「return a;」のような返却値についても、ある関数内で変数が計算結果でa=6となった場合に、関数終了の返却値で「return a;」によって、aの数値を親(呼び出しもとの関数に返却しても、返却値を受け取った側の親の関数は、単に「6」という数値を受け取ることになり、その数値6が変数aに由来する数値なのか、それとも変数bに由来する数値なのかは、受け取り側の(呼び出し元)関数にとっては不明なのです。
 
 
* 子(呼び出し先関数で、親(呼び出し元にある変数に影響を与える非局所な操作したい場合の対策
しかし、裏を返すと、C言語による「局所変数」機能の実現のための新アドレス準備・配当の仕組みのために、もし呼び出した子サイドの関数内で、親サイド(呼び出し元の関数内にある値を変更しようとするとき、その実現がややこしくなってしまっている。
 
もし、子サイド(呼び出された関数内で、親サイド(呼び出し元の関数内にある同名の引数を変更したい場合にはアドレス下記値を指定して引数2つ内容を変化させる必要方法がある。そして、アドレスを使用するために、ポインタ機能を使うことになる。
 
:static変数という、親子で共有する特殊な型の変数を宣言する方法。
:アドレスの値を指定して引数の内容を変化させる方法。なお、アドレス指定を使用するには、ポインタ機能を使うことになる。
 
では、なぜ、ポインタやアドレスを使うと、呼び出し先関数で、呼び出し元の同名の引数を操作できるようになるのか? 証明は、以下の通り。
 
:まず、パソコン視点から見れば、たとえポインタやアドレス機能を使って子サイドを呼び出したところで(たとえアドレスを実引数(呼び出し元親サイド関数の引数)として呼び出しところで、ポインタを仮引数(子サイド(呼び出し先関数の引数)として呼び出したところで、)パソコンは、通常の関数呼び出しで引数を新アドレスにコピー代入するのと同様に、その引数にされたポインタの値やアドレスの値も新アドレスにコピー代入しようとするのである。(けっして、呼び出し方法時のコピー作業が、ポインタ使用時と非使用時で有無が変わるわけではない。)
 
:なので、アドレスが引数にされた関数呼び出しのさいにも、パソコンは新アドレスに呼び出し元引数のアドレス値をコピーする。
 
:だが、関数呼び出しのさいに新アドレスに親サイド(呼び出し元引数のアドレス値をコピーしたところで、けっして、ポインタに格納されている大もとの親サイド(呼び出し元引数のアドレスの種類が増えるわけではない。
 
:そのため、呼び出し先の関数のポインタに格納されているアドレスの値は、大もとの親サイド(呼び出しもと元)で使っている引数のアドレスの値と同じ値である。
 
:なので、子サイド(呼び出し先の関数のポインタにあるアドレス値をつかって、親サイド(呼び出し元の引数のアドレスを操作できる。
 
 
このようにして、アドレスやポインタを使うことにより、子サイド(呼び出し先の関数内で操作をした場合に、呼び出し先親サイドも含む範囲(子サイドの関数外にも影響を与える非局所的な操作をすることができる。
}}
 
116 ⟶ 128行目:
このように、C言語の(ユーザーが自分で定義する)「関数」は、かなり特殊なシステムである。
 
実用では、子サイド(呼び出し先の関数で、親サイド(呼び出しもとの変数の値も変更したい場合も多く、その場合には、「 static 変数」(静的変数)もしくは「ポインタ」という機能をつかう。
 
static 変数とは、変数の宣言時に
static int x;
などと static をつけて宣言すると、その名前の変数は、どこの変数で呼び出されても、共通のアドレスで管理しているので、呼び出した(子サイドの)関数の先で、親サイド(呼び出し元の関数も書き換えできる機能をもつ。
 
いったい何が「静か」(static)なのか不明であるが、おそらく、関数呼び出し時にもアドレス領域を新規確保しないままでいるという意味で、「静か」なのだろう。
 
 
「static」 (スタティック、静か)という名称からは想像しづらいが、staticで宣言された変数は、その宣言のされた場所の関数を親サイドと認識して、その親関数から呼び出された各関数を子サイドと認識する。そして、子サイド(呼び出し先の各関数ブロック内と、親サイド、その変数が「共有」され、さらに、子(呼び出し先の各関数ブロックによる親サイドの変数値の「書き換えが可能」なのである。変数のアドレスが共有された結果、書き込み場所が1つ(その書き込み先の場所は、宣言した関数ブロックにある)しかないので、自動的に、呼び出し先の各関数ブロックで計算した内容が、宣言場所の変数にも代入されてしまうわけである。
 
子サイドの関数が2個以上の複数ある場合、それぞれの子が親と共有するので、結果的に、子関数どうしでも変数を共有することになる。
 
 
もし、「親子共有変数」とか「呼び出し先関数からでも書き換え可能な変数」とか、そういう名前がついていたら分かりやすいが、残念ながら歴史的な理由により、「静的変数」という名前がついてしまった。
 
 
さて、C言語の入門書での「関数」の章の前半に書いてあるプログラムで、static変数やポインタを使わなくても利用できる(ユーザー定義)関数は、むしろ珍しいプログラムなのである。画面に文字などを表示するだけなら、ポインタを使わなくても、そのようなアルゴリズムを構築できるので、そのようなプログラムがC言語の入門書の『関数』の章に書かれるのであろう。
 
なお、「return a;」などのように戻り値をつかって 計算結果の数値を 親(呼び出しもとの関数に送信したりする方法は、じつは、あまり実用的ではなく、現実には不便な場合が多いのである。
 
なぜなら、
:まず、親(呼び出しもとの どの変数に値を送るかを、子(呼び出し先のreturnからは指定できないし、(けっして、親(呼び出しもとの変数aなどを指定して、数値を代入してくれるわけではない)
:しかも、その関数が終了してしまうので、ほかにも処理をその関数で続行したい場合に不便であるし、
:しかも、1つの数値しか送れない、
293 ⟶ 308行目:
 
 
もし「main関数から関数Aを呼び出して、関数Aからは関数Bと関数Cを呼び出す仕様。関数Bで計算した結果の変数を、関数Cでも利用する。」などのコードが3階層(mainの階層、Aの階層、BとCの階層)に分かれた仕様のプログラムなら、変数を共有したい関数Bと関数Cの共通の親(呼び出し元である関数Aで変数宣言をしてstatic宣言をするのが、バグ発見をしやすいプログラムになるだろう。
 
 
357 ⟶ 372行目:
 
関数原型の記述は次のようになっている。
 
<source lang=c>
返却値のデータ型 関数名(引数のリスト);
</source>
 
引数のリストの記述は次のようになっている。
 
<source lang=c>
引数のデータ型 引数名
</source>
 
引数のリストには、引数がない場合はvoidのみを書く。
複数の引数がある場合、コンマで区切る。
441 ⟶ 460行目:
==== static変数を使う ====
:(※ 未記述)
上述したとおり。
 
 
==== ポインタを使う方法 ====
ポインタを使う方法である。しかし、ポインタを使えば、2つ以上の数値を操作する事ができるので、ポインタを使って、親(呼び出し元関数の複数の値を操作する事ができるので、それを「複数の返却値である」と解釈する事もできる
 
このような事を、「ポインタによる参照渡しの引数を、返却値として用いる」などと言ったりするが、要するにポインタを使って親(呼び出し元の変数の値を操作するというだけの事であり、あまり深い意味はない。
 
<source lang=c>
526 ⟶ 545行目:
</source>
 
なお、子(呼び出し先 void swap(int a, int b){ a, b の部分仮引数と呼ぶ。
またなお、親(呼び出し元 swap(a, b); a, b の部分実引数と呼ぶ。
 
この例は意図通りに動作しない。何故なら実引数の値が仮引数にコピーされたためである。
616 ⟶ 635行目:
たいていのプログラムはこのmain関数から始まると考えてよい。
main関数には大まかに分けてプログラム仮引数(コマンドライン引数)を持たない関数と持つ関数との2種類の記述方法がある。
 
* プログラム仮引数とは
プログラムを実行する際、プログラムに対して渡される情報を、プログラム仮引数と呼ぶ。
例えばコマンドプロンプトにおいて、
 
<source lang=c>
C:\example.exe ABC DEF GHI
</source>
 
のようにプログラムを実行した場合、
example.exeプログラムのmain関数に対して、
「example.exe」、「ABC」、「DEF」、「GHI」という4個の文字列が渡される。
 
* プログラム仮引数を持たない関数
プログラム仮引数を使用しない場合、次のように仮引数をもたない関数を記述する。
 
<source lang=c>
int main(void)
633 ⟶ 657行目:
}
</source>
 
* プログラム仮引数を持つ関数
プログラム仮引数を使用する場合、次のように2つの仮引数をもつ関数を記述する。
<source lang=c>
641 ⟶ 666行目:
}
</source>
 
argcはプログラム仮引数の個数である。
argvはプログラム仮引数を表す文字列の配列へのポインタである。
argv[0]が指す文字列はプログラム名である。
argcにはこのプログラム名も個数として数えられる。
 
ただし、argcが0の場合、argv[0]は空ポインタである。
またargc>0であっても、ホスト環境からプログラム名が得られない場合、
652 ⟶ 679行目:
argv[argc]は空ポインタである。
<ref>『JISX3010:2003』p.8「5.1.2.2.1 プログラム開始処理」</ref>
 
<source lang=c>
//例 プログラム仮引数
723 ⟶ 751行目:
 
関数のメモリアドレスを関数へのポインタに格納する記述は次のようになっている。
 
<source lang=c>
関数へのポインタ名=関数名;
</source>
 
関数名の後ろに、「()」はいらないことに注意。
 
731 ⟶ 761行目:
間接的に関数を呼び出すには、
次のように記述する。
 
<source lang=c>
(*関数へのポインタ名)(引数のリスト)
815 ⟶ 846行目:
再帰を行うには、その関数がリエントラントである必要がある。
リエントラントであるためには、使用する変数を共有しないように気を付ける必要がある。<ref>『再入可能とは 「リエントラント」 (reentrant) さいにゅうかのう: - IT用語辞典バイナリ』http://www.sophia-it.com/content/%E5%86%8D%E5%85%A5%E5%8F%AF%E8%83%BD</ref>
 
<source lang=c>
//例 再帰を使った階乗の計算
853 ⟶ 885行目:
 
== 参考文献 ==
* 日本工業標準調査会『JISX3010 プログラム言語C』2003年12月20日改正
 
[[Category:C言語|かんすう]]