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

削除された内容 追加された内容
Ef3 (トーク | 投稿記録)
タグ: 2017年版ソースエディター
Ef3 (トーク | 投稿記録)
→‎コラム: 関数呼出しABIを誤解し多くの誤りがあり訂正できる規模でないので削除、少なくともスタックフレームの正確な理解をした上で、具体的なアセンブリーコードを使って説明すべき。
タグ: 2017年版ソースエディター
169 行
 
下記に述べるように、少なくとも以下の3つの方法があります。
 
=== コラム ===
{{コラム|局所変数とアドレス操作の関係|
* 局所変数の実現方法
このようなユーザー定義の関数で、呼び出し先関数での引数(上記の例では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変数という、親子で共有する特殊な型の変数を宣言する方法。
:アドレスの値を指定して引数の内容を変化させる方法。なお、アドレス指定を使用するには、ポインタ機能を使うことになる。
 
では、なぜ、ポインタやアドレスを使うと、呼び出し先関数で、呼び出し元の同名の引数を操作できるようになるのか? 証明は、以下の通り。
 
:まず、パソコン視点から見れば、たとえポインタやアドレス機能を使って子サイドを呼び出したところで(たとえアドレスを実引数(親サイド関数の引数)として呼び出しところで、ポインタを仮引数(子サイド(呼び出し先)関数の引数)として呼び出したところで、)パソコンは、通常の関数呼び出しで引数を新アドレスにコピー代入するのと同様に、その引数にされたポインタの値やアドレスの値も新アドレスにコピー代入しようとするのである。(けっして、呼び出し方法時のコピー作業が、ポインタ使用時と非使用時で有無が変わるわけではない。)
 
:なので、アドレスが引数にされた関数呼び出しのさいにも、パソコンは新アドレスに親(呼び出し元)引数のアドレス値をコピーする。
 
:だが、関数呼び出しのさいに新アドレスに親サイド(呼び出し元)引数のアドレス値をコピーしたところで、けっして、ポインタに格納されている大もとの親サイド(呼び出し元)引数のアドレスの種類が増えるわけではない。
 
:そのため、呼び出し先の関数のポインタに格納されているアドレスの値は、大もとの親サイド(呼び出し元)で使っている引数のアドレスの値と同じ値である。
 
:なので、子サイド(呼び出し先)の関数のポインタにあるアドレス値をつかって、親サイド(呼び出し元)の引数のアドレスを操作できます。
 
 
このようにして、アドレスやポインタを使うことにより、子サイド(呼び出し先)の関数内で操作をした場合に、親サイドも含む範囲(子サイドの関数外)にも影響を与える非局所的な操作をすることができます。
}}
 
=== 「サブルーチン」との違い ===