(教科書5章 記憶クラスと通用範囲)
専門用語(technical term)が色々出てきますが、静的変数とスコープの概念が解れば十分です。
前回は、複雑で規模の大きいプログラムを書くために,プログラムを複数の関数に分けて書く話をしました。
この中で、スタックと呼ばれる仕掛けで変数のメモリー割り当てが行われることを紹介しました。このようにしてメモリーを割り当てる変数を自動変数と呼びます。自動変数は関数のブロックの中でだけ有効な変数で、関数から戻ると割り当ては解除され、解除されたメモリーは次のメモリー割り当てで再利用されます。
今回は自動変数に対して,静的変数を紹介します。 この変数はメモリー割り当てをプログラムの実行開始前に行い、プログラム実行中は割り当てを変更しません。変数の有効期間がプログラム実行中の全期間です。
静的変数は関数間で共有する変数としても使えます。あるいは,関数が呼ばれた回数を数えるようなことにも使えます。
さらに今回は関数名や変数名のスコープ(通用範囲)を制限して,名前の重複を避ける工夫を紹介します。規模の大きいプログラムでは無数の関数名や変数名が使われるので,重複を避けるための仕組みが必須になります。
※スコープ(通用範囲):変数名や関数名で参照できる範囲、言い換えると、結合(名前と名前の対応付け)が行われる範囲です。ブロックの内側で定義された自動変数名は,そのブロック内で同じ名前で参照できます。このスコープをローカル・スコープ(あるいはブロック・スコープ)と呼びます。ローカル・スコープの中で名前の対応付けが完結する結合を無結合と呼びます。
ローカル・スコープ外との名前と対応付ける場合,その範囲が翻訳単位内(ファイル・スコープ内)であれば内部結合,翻訳単位外のプログラム全体(グローバル・スコープ内)であれば外部結合と呼びます。
プログラムやデータを記憶する場所を記憶領域と言います。 記憶領域はプログラム( 機械語命令)を記憶するコード領域(code area/text area/program areaと様々)とデータの記憶に使われるデータ領域(data area)に2分されています。
データ領域は、さらにメモリーの割り当て方でスタック領域(自動記憶域)、スタティック領域(静的記憶域)、ヒープ領域の3つに分けられます。この他にもデータを記憶する場所としてメモリーではなくて、CPU内のレジスタが使われることもあります。
C言語では関数やブロックの中で変数を定義できる。これらの変数はその関数やブロック内でのみ有効な変数なので局所変数(ローカル変数)と呼ばれます。
記憶クラス指定子autoを付けて定義した局所変数は自動的にメモリーの割り当てと解除が行われる自動変数となります。しかし,局所変数の規定値は自動変数なのでautoは省略可能です。従って、autoを記述する必要性は無い^^。
これまでのプログラムで定義してきた変数は全て自動変数です。
※記憶クラス指定子staticを付けて定義した局所変数は静的変数になる。
局所変数は自動変数(規定値)か静的変数の二択。
※記憶クラス指定子:auto,static,extern,register,typedef の5種類がある。
自動変数はブロックに入るときに前回説明したスタックの仕組みを使ってメモリ割り当てを行います。その意味でスタック領域に割り当てられる変数ともいえます。割り当てが解除されるのはブロックから出るときで、解除されたメモリは他の変数の割り当てに再利用 されます。
※コンパイラは最適化を行いスタックではなくてレジスタを変数の記憶場所とすることがある。
自動変数の有効期間は定義したブロックに入ってから出るまでです。
※自動変数の名前に対応するメモリー番地はスタックポインタの値からの相対アドレスで決まる。関数呼び出しの度にスタックポインタの値は更新されるので、関数の再起呼び出しを行う次のようなプログラムを作ることができる。
long factorial(int n)/*仮引き数も自動変数*/ { long ret;/*自動変数*/ if(n==0) ret=1; else ret=n*factorial(n-1);/*この様な再帰ができるのは自動変数を使うから*/ return ret; }
自動変数のメモリ割り当てでは、初期化の記述が無ければ初期化は行いません。スタックのメモリーを使いまわしているので、前の値がメモリーに残っています。しかし、初期化するためにはメモリに値を書き込む処理が必要です。初期化が不要な変数では、この時間を節約できるようにしているのがC言語です。
大量のメモリーを必要とする変数(数MBを超える大きな配列など)を自動変数にするとスタック領域からあふれることもある。スタック領域を広くすることもできるが、必要なメモリーの大きさが分かっているなら後述する静的変数に、わからない場合はヒープ領域への割り当ても検討しよう。
計算機が計算に使えるメモリの中で最も高速に読み書きできるのはCPUに内蔵されたレジスタです。レジスタは数が少なく多くの変数をレジスタに割り当てることはできないが、旨く使えばプログラムの高速化に大きな効果が期待できる。このためC言語では記憶クラス指定子registerを付けて定義するレジスタ変数を用意している。この変数はレジスタに空きがあればレジスタに割り当てられるが詰っていればスタックに割り当てられる。
※レジスタ変数はメモリーアドレスが無いのでアドレスを求めたりポインタで参照したりもできない。
多くのコンパイラが翻訳時に最適化を行いレジスタに割り当てることが適当な変数はレジスタに割り当てる。プログラマーがレジスタ変数を指定する場合は機械語への翻訳結果を熟知して行うこと。
レジスタ変数を序論レベルで使う必要は無いので、これ以上の説明は行わない。
double sum(void)
{
int i;/*このiも最適化でレジスタに割り当てられる可能性が高い*/
register double s=0;/*可能ならレジスタに割り当て*/
for(i=1;i<=100;i++)s+=1.0/i;
return s;
}
プログラムの起動時にメモリーが割り当てられ、プログラム終了まで割り当てが変化しない変数を静的変数と呼びます。この変数を割り当てるメモリー領域がスタティック領域です。
静的変数の割り当てはプログラムの起動時(main関数を呼ぶ前)に一度だけ行い、同時に初期化も行います。初期値が示されていない場合は0で初期化します。
静的変数は割り当てが変化しないので、変数はプログラムの実行開始から終了までの全期間で有効です。この特性を利用して、静的変数を使って関数間でデータ交換を行うこともできます。
C言語では、関数の外に定義した変数は大域変数と呼びます、関数の名前と同じようにプログラムの広い範囲から名前で参照することができます。大域変数は全て静的変数になります。記憶場所の割り当てが変化することが無いので、多くの関数から名前で参照でき、データの受け渡しに使えます。
※大域変数はグローバル変数あるいは広域変数とも呼ばれます。しかし、大域変数にも参照を翻訳単位内に限定する内部変数(ファイル・スコープの変数)と翻訳単位外のプログラム全体に許す外部変数(グローバル・スコープの変数)があります。グローバルという呼び方に引きずられてグローバル変数を外部変数と混同しないでください。
※翻訳単位:C言語ではソースファイル単位でコンパイルを行いオブジェクトファイルを作ります。したがって、ソースファイルが翻訳単位となります。(正確にはコンパイルの前に前処理が行われるので、ソースファイルに前処理を行ったものが翻訳単位)
しかし、広い範囲から使える変数を用いると、その変数を何処でどの様に使っているかに気を使ってプログラミングしなければなりません。
大規模なプログラムになるほど、このような何処からでも使える変数を使うと動作が読みにくく、修正も難しいプログラムとなります。このため、広い範囲から使える変数は他に手段が無いときにのみ使ってください。序論の範囲では極力使わないことにします。
※関数の外に定義した変数を使う場合は 後述するスコープ(通用範囲)を利用して使える範囲を制限するようにしましょう。
#include<stdio.h>
int count=0;/* mainから見えるように関数外で定義した大域変数*/
int factorial(int n)
{
count++;
if(n==0)return 1;
else return n*factorial(n-1);
}
int main(void)
{
printf("5!=%d\n",factorial(5));
printf("関数factorialが呼ばれた回数は%d\n",count);
return 0;
}
これまで関数内で定義した変数は関数から戻ると無効になりました。無効にならない変数を作りたいとき、
staticを付けることで関数内に静的変数を定義できます。
※この変数は局所変数なので他の関数からは参照できません。従って、他の関数への情報伝達には使えません。
2つの静的変数markとcounterを定義しています。静的変数の初期化はプログラムの起動時に一度だけ行われます。
counter=0のように初期化が書いてあります。しかし、この初期化はプログラムの起動時に一度だけ行われます。関数が呼ばれた時のcounterの初期化は行いません。
次の例のように、呼ばれた回数で処理が変わるような関数を作ることができます
#include<stdio.h> char mark='+';/*関数の外に定義した変数は全て静的変数*/ void print(char c) { static int counter=0;/*static付きで定義した局所変数は静的変数になる*/ putchar(c); counter++; if(counter==10)/*10回目のprint呼び出しでmarkを印字する/ { putchar(mark);/*関数の外で定義した変数は定義後はどこでも使える*/ counter=0; } } int main(void) { int i; for(i=0;i<40;i++) print('a'); putchar('\n'); mark=' ';/*関数の外で定義した変数はは定義以降はどこでも使える*/ for(i=0;i<40;i++) print('a');/*printを40回連続で呼び出し*/ putchar('\n'); return 0; } /* 実行結果 aaaaaaaaaa+aaaaaaaaaa+aaaaaaaaaa+aaaaaaaaaa+ aaaaaaaaaa aaaaaaaaaa aaaaaaaaaa aaaaaaaaaa */
C言語では静的変数と自動変数の他に、プログラムの実行中に必要なメモリーを確保したり、解放したりできる動的メモリー割り当てと呼ばれる仕組みが用意されている。 この動的メモリー割り当てを行うメモリ領域をヒープ領域(heap area)という。mallocやfree等の関数を用いてプログラム中でメモリーの確保と解放を行い、ポインターを使うなどして確保したメモリーを利用する。
割り当てが有効な期間はメモリー確保から解放するまでの期間でプログラムにより自由に制御できる。
※この領域を教科書では割り付け記憶域と呼んでいる。
ヒープからのメモリ確保と解放については後期の序論演習IIのポインターのところで説明することにしました。ここでは、説明を省きます。
メモリ領域 メモリーの内容 有効期間 メモ コード領域 機械語
プログラムプログラムの開始から終了まで 関数などの機械語プログラムを格納する場所 スタティック領域 静的変数 プログラムの開始から終了まで 静的変数を割り当てる領域で、main関数実行前に一度だけ初期化される スタック領域 自動変数
レジスタ変数関数やブロック内を実行中の期間
スタックと呼ばれる仕組みでメモリを割り当てる。
関数からの戻り番地、仮引数、自動変数を割り当てる
自動的な初期化はしないレジスター 高速な読み書きが行える。演算回路の入力や出力の保持、レジスタ変数や関数の戻り値の記憶にも使われる。 ヒープ領域 利用制限なし
多目的利用可確保から解放まで 実行中に新たに確保と解放が可能なメモリ領域
有効期間をプログラムで制御できる。
|
メモリーの確保
実行開始時に確保される領域 実行中に確保される領域 スタック領域 スタティック域 ヒープ領域
スタック領域: スタティック領域:
ヒープ領域: |
プログラムでは変数名や関数名が重複してはいけない。この条件を守ることはプログラムが大規模になったり、複数の人が共同で作る場合は容易ではありません。
昔々何人かでBASIC言語でプログラムを共同作成するときには名前が衝突しないように、関数名や変数名の付け方をまず話し合う必要が有りました。この様な名前の衝突問題を軽減するには名前の通用範囲:スコープを制限すると効果的です。 通用範囲を制限すれば、通用範囲内で名前が重複しないようにすればいいからです。
序論では下記の3種類のスコープに分けて説明します。本当はもっと細かい種類わけがあるのですがここでは大まかに3つに分けます。
Cの実行可能プログラムはライブラリやオブジェクトファイルを組み合わせて作られます。オブジェクトファイルはソースファイルをコンパイルしたもの。ライブラリは複数のオブジェクトをまとめたものです。
C言語では複数のファイルに分けてプログラムを作ることができるので、プログラム全体を通用範囲とするグローバル・スコープだけでなくファイルに通用範囲を限定したファイル・スコープを用意しています。
C言語では1個のソースファイルを前処理し 、これを翻訳して1個のオブジェクトファイルを作成します。そこで、ソースファイルに前処理でヘッダーファイルの挿入等が行われたテキストを翻訳単位と呼びます。
※翻訳単位を翻訳したオブジェクトファイルは教科書でモジュールと呼ばれているものの一種です。 C言語のソースファイルをコンパイルしたオブジェクトファイルだけでなく、初めから機械語で書かれたプログラムファイルや他の言語で作られたオブジェクトファイルなどもモジュールとして扱うことができます。
ソースファイルをコンパイルする場合を考えてみましょう。関数の仮引数や、関数の中で定義された自動変数はコンパイルの時点でスタックポインターからの相対アドレスが決まるので、コンパイルの段階で機械語に組み込むことができます。これに対して、例えばprintf関数のような関数の呼び出し先のアドレスはまだ未定なのでオブジェクトファイルの中には具体的なアドレスを入れることはできません。オブジェクトファイルには未定の部分が空欄で残る機械語コードと未定部分のリストが保存されています。
リンク処理では、オブジェクトファイルやライブラリーから必要な関数名に対応する機械語プログラムを集めて並べます。各関数の先頭アドレスを決め、機械語の空欄部分にアドレスに書き込んでプログラムを繋ぎます。
静的変数についても同様に必要なメモリーの大きさが分かるのでスタティック領域内のアドレスを決めて、機械語プログラム内の対応部分にアドレスを書き込みます。
リンカーは,このように関数や変数の名前とメモリーアドレスの対応付けを行います。
コンパイルで作られるオブジェクトファイルと,リンクした実行可能ファイルの比較
※リンクはプログラムをメモリー上に展開する時点で行ったり、実行中に行ったりもできます。
C言語では関数ブロック内のプログラムは外から参照できないルールとしています。従って、ブロック内で定義された変数へ外から結合することはできません。
※逆に,ブロック内から外へ結合することは可能です。
これに対して関数や大域変数は他から参照(呼び出しや読み書き)できるので、使う側との結合の範囲が指定されています。C言語の関数や大域変数では、翻訳単位外とも結合する外部結合が既定の結合です。しかし、名前の衝突を避けるため、翻訳単位に限定した内部結合も指定できます。
※ 複数の翻訳単位に分割してプログラムを作成する場合、外部結合する名前はプログラム全体で一意でなければならない。従って、外部結合する名前は、プログラミング作業を分割する時に,決めておく必要があります。これに対して、内部結合の名前は各翻訳単位を作るプログラマが自由に決めても他に影響は出ません。
翻訳単位の中の構造を次に示します。ここでexternは外部結合する名前、staticは内部結合する名前を示す記憶クラス指定子です。
外部結合の名前の通用範囲をグローバル・スコープと言います。 外部結合が許された関数や変数は他の翻訳単位に定義されたものでも、外部結合により参照できます。
(グローバル・スコープでは翻訳単位の垣根を超えて参照可能)
しかし,コンパイラは翻訳単位を上から順番に処理するので、名前を使う前に宣言または定義が必要です。他の翻訳単位で定義された外部結合の名前を使うときは、各翻訳単位で名前の宣言が必須です。これを簡便に行うためにヘッダーファイル挿入の仕組みが使われます。
表:記憶クラス指定子externを使用した関数ブロックの外の宣言。
宣言の種類 | 宣言の意味 |
変数 | |
変数の定義 | 外部変数の定義 externは規定値で省略可 |
変数の定義でない宣言 | 外部変数の定義でない宣言 extern必要※1 |
関数 | |
関数の定義 | 外部結合関数の定義 externは規定値で省略可 |
関数のプロトタイプ宣言 | 外部結合関数のプロトタイプ宣言 externは規定値で省略可 |
大域変数は記憶クラス指定子が省略されたときは外部変数(外部結合の変数)となります。関数と同様に外部変数の定義ではない宣言を行えば他の翻訳単位からも参照可能です。
※1 外部結合の変数宣言でexternを省くと、変数の定義とみなされる。外部変数の定義でない宣言のみを行うときはexternを省略できない。externが付いていても初期化の記述があるものは定義となる。
関数定義は規定値で外部結合関数(外部結合の関数)です。このため, 記憶クラス指定子externは省略されるとが多い。
自分のソースファイル内でもexternでの宣言は有効です。 これを用いると参照の前に宣言があれば、参照の後に定義が来てもOKです。関数のプロトタイプ宣言と定義の関係と同じです。
#include<stdio.h> extern int a;/*整数型のaという外部変数の存在を宣言*/ /*この1行が無いと下の参照でコンパイルエラー*/ /*(注意)記述で初期値を指定すると定義となる*/ int main(void) { printf("%d\n",a);/*定義の前に参照を書いている*/ return 0; } int a=12;/*ここで変数aを定義している*/ /*この1行が無くてもオブジェクトファイルへのコンパイルはできる。*/ /*しかしリンクではリンクエラーで外部変数aが見つけられず実行可能ファイルは作れない*/
内部結合の名前の通用範囲をファイル・スコープと言います、名前が宣言あるいあは定義された翻訳単位が通用範囲です。
(ファイル・スコープでは翻訳単位内に参照を制限)
※コンパイラは翻訳単位を上から順番に処理するので、名前を使う前に宣言または定義が必要です。
表:記憶クラス指定子staticを使用した関数の外の宣言。
宣言の種類 | 宣言の意味 |
変数 | |
変数の定義 | 内部変数の定義 |
変数の定義でない宣言 | 内部変数の定義でない宣言 |
関数 | |
関数の定義 | 内部結合関数の定義 |
関数のプロトタイプ宣言 | 内部結合関数のプロトタイプ宣言 |
内部結合の変数を内部変数と言います。関数の外で、記憶クラス指定子staticを付けて宣言や定義を行う必要が有ります。内部変数はファイル・スコープの大域変数です。
ローカル・スコープ(関数内)にstatic付きで定義した静的変数は無結合で内部変数ではありません。記述が衝突するので,関数内では内部変数の定義だけでなく,宣言もできません。同じstaticを使っていますが,違う意味になるので混乱しないようにしてください。
内部結合の関数を内部結合関数と言います。記憶クラス指定子staticを付けて定義を行う必要が有ります。内部結合の関数なので、翻訳単位外から呼び出すことはできません。この関数のプロトタイプ宣言はstaticを付けることが必須です。
※static付きでプロトタイプ宣言し、後でstaticなしで同じ翻訳単位内で関数定義したプログラムはエラーになりません。
分割コンパイルを行う場合は,外部結合の必要が無い関数や変数の定義は内部結合にしましょう。翻訳単位外から参照される心配がありませんし、名前の衝突を防げます。
#include<stdio.h> extern void reset();/*外部結合関数*/ extern void countUp();/*外部結合関数*/ extern int getCount();/*外部結合関数*/ /* externは省略できますが このファイルの外に定義が有ることを強調する意味で付けました */ int factorial(int n) { countUp(); if(n==0)return 1; else return n*factorial(n-1); } int main(void) { int i; reset(); factorial(5); printf("factorialの呼ばれ回数は%d\n",getCount()); return 0; }
/* カウンター:プログラム部品 このカウンターは外部からcountを直接参照したり変更したりできない。 このファイル内で定義された関数を用いてのみ利用できる。 */ static int count=0; /*変数countはファイル・スコープ。ファイル外からの参照は不可*/ void reset(void) /*externは省略しました*/ { count=0; } void countUp(void) /*externは省略しました*/ { count++; } int getCount(void) /*externは省略しました*/ { return count; }
関数内や{と}で囲まれたブロック内で宣言された名前はその関数やブロック内でしか使えません。このような名前の通用範囲をローカル・スコープと言います。※別名ブロックスコープ(block scope)とも言う
(ローカル・スコープではブロック内に参照を制限)
ブロック内で宣言された名前は全て宣言したブロック内のローカル・スコープです。外からブロックの中にあるものを参照することはできないルールです。 逆にブロックの中から外を参照することは可能です
※関数定義の仮引数名は関数のブロック内で定義していませんが、引数列の記述から関数ブロックの終端までが通用範囲です。関数定義の仮引数は関数の一番外側のブロックにあるとみなされ,ローカル・スコープとなります。
表:ローカル・スコープ内の宣言に対する記憶クラス指定子の意味。
宣言の種類 | autoは規定値 普通は記述しない |
static | extern |
変数の定義 | 自動変数の定義 | 静的変数の定義 | 不可 |
変数の定義でない宣言 | 不可 | 不可 | 外部変数の定義でない宣言※1 |
関数の定義 | 不可 | 不可 | 不可 |
関数のプロトタイプ宣言 | 不可※2 | 不可※2 | 外部関数のプロトタイプ宣言※2 |
autoは自動変数を指定する記憶クラス指定子です。ブロック内でしか使えません。しかし、規定値であるため省略することができます。このためautoは記述を省略するのが普通です。
記憶クラス指定子staticは内部結合とは違う静的変数の意味でつかわれていますから注意してください。無結合の名前となります。
記憶クラス指定子externを付けてブロック内に名前を宣言できます。参照する相手はブロックの外の外部変数や外部結合関数ですが、この定義でない宣言の有効範囲はブロック内に限定されるローカル・スコープです。
※1 これはブロックの内側から関数ブロックの外側にある外部変数を参照(読み書き)するために行う宣言です。この宣言された名前の通用範囲はローカル・スコープです。以下に例を示す。(外部変数については別に述べる)
最後の行のグローバル変数markは外部結合でグローバル・スコープを持つ。しかし、最後の行に定義されているため、関数mainやpでは事前の宣言が無いと使えない。ここでmain関数の内側でmarkが外部変数の名前であることを宣言すると、main関数のローカル・スコープ内でのみ外部変数markを使えるようになる。
#include<stdio.h> void p(void); int main(void) { extern char mark;/*この宣言で識別子markを参照できる*/ putchar(mark); p(); return 0; } void p(void) { /*ここではmain関数中でのmarkの宣言はスコープ外で見えない*/ putchar(mark);/* markは「定義されていない識別子」でコンパイルエラー*/ } char mark='+';/*最後に置いてpから参照不可にした*/
※2 ブロックの内側での関数プロトタイプ宣言の記憶クラス指定子はexternのみ使用可能(JIS規格 X3010_01 71page)。しかし、Visual Studioではauto、static、externのいずれの場合でもファイル・スコープとして解釈される。以下のプログラム例はVisual Studioではファイル・スコープへの拡張警告付きでコンパイル可能。
#include<stdio.h> void f(void); int main(void) { extern void g(void);/*ローカル・スコープの宣言がファイル・スコープに拡張される*/ f(); return 0; } void f(void){ g();/*main関数内のプロトタイプ宣言がファイル・スコープに拡張されて、無警告*/ printf("FFFF\n"); return; } void g(void) { printf("GGGG"); return; }
ブロックの中に入れ子状にブロックを含めることが可能です。この場合も各ブロックの先頭でブロック内でのみ参照できる変数を宣言できます。名前のスコープはその名前が宣言・定義されたブロックの 内側となります。もし名前が衝突している場合は内側のブロックで宣言や定義を行った名前が有効です。以下に例を示します。
#include<stdio.h> int main(void) { /*関数ブロック*/ int i=123;/*関数ブロックのローカル変数*/ printf("%d\n",i); { /*外側の変数iと同じ名前の変数を使いたいのでブロック化*/ int i; for(i=0;i<10;i++) printf("%c",'-'); printf("\n"); } printf("%d\n",i);/*for文のために変数iを用いた影響は?*/ return 0; } /*実行結果 123 ---------- 123 */
※C99では使う前であれば、ブロック先頭以外でも宣言や定義が可能になった。しかし、序論の範囲ではブロック先頭での宣言や定義のみとしておく。途中での宣言や定義を無規範に行うと非常に読み辛いプログラムになるし、C99未対応のコンパイラでは翻訳できなくなる。
この課題はグローバル変数:counterと同じ名前の仮引数を使った関数fの内側で、グローバル変数counterをカウントアップする方法を問うものです。
関数やブロックの内側で宣言された名前が優先するので、関数fの内側ではcounterという名前は仮引数のことになってしまいます。そこで、 グローバル変数をcounterという名前で使いたいところを新たなブロックとして.何とかしようとしています。書き足すのは、たった1行です。人に聞かずに試行錯誤してみてください。
注意:
初期ファイルをそのままコンパイルし実行すると無限ループのプログラムになり止まりません。
レポートツールの「実行」ボタンが「中断」ボタンになっているはずです。
この「中断」ボタンを押すとプログラムを止めて実行終了します。
#include<stdio.h> int counter=0;/*外部変数の定義*/ int f(int);/*外部関数の宣言*/ int main(void) { int f10; counter=0; f10=f(10); printf("関数f(10)の値は%d ",f10); if(f10==89)printf("○\n");else printf("×\n"); printf("関数fが呼ばれた回数は%d回 ",counter); if(counter==177)printf("○\n");else printf("×\n"); return 0; } int f(int counter)/*課題なのでわざと名前を衝突させて仮引数を作っている*/ { { /*この新しいブロックで外部変数のcounterを1つ増やそう*/ ****** ここで変数名counterが外部変数のcounterであることを宣言すればいい ***** counter++; } /*以下のcounterは仮引数のcounter*/ if(counter==0)return 1; else if(counter==1)return 1; else return f(counter-1)+ f(counter-2); } /*実行結果 関数f(10)の値は89 ○ 関数fが呼ばれた回数は177回 ○ */