C言語/関数

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動

関数の基本[編集]

C言語のプログラムは全て1つ以上の「関数」と呼ばれる処理のかたまりから構成される。

関数を用いて、いくつかの文をブロックでまとめることができる。「main関数」も関数であり、「printf」や「scanf」なども関数であるが、それとは別に、ユーザーがいくつかの処理をまとめて「関数」として定義できる。

C言語でいう、ユーザーの定義する「関数」は、よそのプログラム言語でいう「サブルーチン」のような性質がある(ただし、けっして、完全に「サブルルーチン」とC言語の関数は同じではない。相違点も存在するが、説明が長くなるので後述する)。

関数を用いることによって、同じ処理をまとめて書くことができ、以後その関数を呼び出すだけでまとめられた処理が実行できるようになる。


関数の定義[編集]

ユーザーが自分で定義する関数について、記法の例を表す。

//例 2つの整数を引数にとりその和を返却する関数
int function(int a, int b)
{
	return a+b;
}

以下、特にことわらないかぎり、ユーザーが定義した関数を、単に「関数」と省略する。main関数やprintfなども「関数」と省略する場合があるので、読者は文脈から判断のこと。

関数を呼び出す際、関数の 呼び出し元(仮に「親」と呼ぼう) から 呼び出し先関数(仮に「子」と呼ぼう)へ情報を渡すことができ、この渡される情報のことを引数(ひきすう、argument)と呼ぶ。

関数の定義の記述は次のようになっている。

返却値のデータ型 関数名(引数の型と引数名)
{
	/*いくつかの文*/
}

関数名とはその関数を他の関数と区別するために付ける名前のことで、 関数名に使えるのは半角英数、「_(下線)」である。 ただし、先頭は非数字でなければならない。 多バイト文字を使用できるかはコンパイラによる。 関数は他の関数の中で呼び出すことができる。

また、引数は、省略できる場合がある。つまり、

返却値のデータ型 関数名()
{
	/*いくつかの文*/
}

のように関数を記述する場合もある。


関数の呼び出し[編集]

定義した関数を呼び出す際の記述は、たとえば次のように行う。

//例 上の例の関数を呼び出す
int function(int a, int b)
{
	return a+b;
}

int main(void){
	int r;
	r=function(1,2);//rには1と2との和である3が代入される。
}


関数の定義側で書かれた引数のことを仮引数(かりひきすう、parameter)という。

いっぽう、関数の呼び出し側で書かれた引数のことを実引数(じつひきすう)という。


複数の仮引数を持つ場合、「,(コンマ)」で区切る。仮引数を持たない場合、何も書かない。

仮引数の記述は次のようになっている。

仮引数のデータ型 仮引数名



任意個数の実引数について、実引数を持たない場合、何も書かない。 複数の実引数を持つ場合、「,(コンマ)」で区切る。

実引数の記述は次のようになっている。

関数名(任意個数の実引数);

また、関数の呼び出し先から呼び出し元へ情報を渡すこともでき、この情報を返却値と呼ぶ。 返却値のデータ型で返却値の有無や種類を決定する。 任意個数の仮引数で仮引数の数や種類を決定する。


この例では、function関数の実引数である1,2が、 function関数の仮引数であるa,bにコピーされ、 a+bがfunction関数の返却値として返され、 その値がrに代入される。

関数を呼び出すとき、 引数と制御が戻るべきアドレス(リターンアドレス)がスタックと呼ばれる領域に格納される。 また、返却値がレジスタと呼ばれる領域に格納される。[1]


引数と返却値[編集]

引数と返却値をもたない関数[編集]

//例 引数と返却値をもたない関数
#include <stdio.h>

void HelloWorld()
{
	printf("Hello, World!\n");
}

int main(void)
{
	HelloWorld();
}

この例では自作のHelloWorld関数を使って文字列を表示している。 このHelloWorld関数は引数と返却値をもたない関数である。 このプログラムでは、 まずmain関数が呼ばれ、 次にmain関数内のHelloWorld関数を呼ぶ文が実行されることでHelloWorld関数が呼ばれ、 HelloWorld関数内のprintf関数を呼ぶ文が実行されることでprintf関数が呼ばれる。 そしてprintf関数が終了してHelloWorld関数に戻り、 HelloWorld関数が終了してmain関数に戻り、 main関数が終了してプログラムが終了する。

この例では関数を自作したメリットはないが、 同じ処理がソースコードのあちこちに出てくる場合などは、 その同じ処理を自作の関数にまとめるとメリットがある。

引数と返却値をもつ関数[編集]

//例 引数と返却値をもつ関数
#include <stdio.h>

double calc(double radius)//「calc関数の定義」
{
	return 3.14*radius*radius;
}

int main(void)
{
	double radius, area;
	printf("円の半径を入力してください。:");
	scanf("%lf", &radius);
	area=calc(radius);//「calc関数の呼び出し」
	printf("円の面積は%fです。\n",area);
}

この例では、自作のcalc関数を用いて、円の半径から円の面積を計算している。 このcalc関数は引数と返却値をもつ関数である。 「calc関数の呼び出し」のところのradiusを実引数と呼ぶ。 「calc関数の定義」のところのradiusを仮引数と呼ぶ。 このプログラムでは、 「calc関数の呼び出し」で、まずmain関数のradiusが実引数としてcalc関数に渡され、 その値がcalc関数の仮引数のradiusにコピーされ、 それから、計算結果が返却値として返され、 その値が変数areaに代入されている。


関数に複数の返却値を持たせる方法[編集]

関数による通常の返却値の方法では、1つの値しか、呼び出し元関数に値を送れない

しかし、特殊な方法を使うことにより、呼び出しもとの関数に、複数の値を送ることができる。

下記に述べるように、少なくとも以下の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変数という、親子で共有する特殊な型の変数を宣言する方法。
アドレスの値を指定して引数の内容を変化させる方法。なお、アドレス指定を使用するには、ポインタ機能を使うことになる。

では、なぜ、ポインタやアドレスを使うと、呼び出し先関数で、呼び出し元の同名の引数を操作できるようになるのか? 証明は、以下の通り。

まず、パソコン視点から見れば、たとえポインタやアドレス機能を使って子サイドを呼び出したところで(たとえアドレスを実引数(親サイド関数の引数)として呼び出しところで、ポインタを仮引数(子サイド(呼び出し先)関数の引数)として呼び出したところで、)パソコンは、通常の関数呼び出しで引数を新アドレスにコピー代入するのと同様に、その引数にされたポインタの値やアドレスの値も新アドレスにコピー代入しようとするのである。(けっして、呼び出し方法時のコピー作業が、ポインタ使用時と非使用時で有無が変わるわけではない。)
なので、アドレスが引数にされた関数呼び出しのさいにも、パソコンは新アドレスに親(呼び出し元)引数のアドレス値をコピーする。
だが、関数呼び出しのさいに新アドレスに親サイド(呼び出し元)引数のアドレス値をコピーしたところで、けっして、ポインタに格納されている大もとの親サイド(呼び出し元)引数のアドレスの種類が増えるわけではない。
そのため、呼び出し先の関数のポインタに格納されているアドレスの値は、大もとの親サイド(呼び出し元)で使っている引数のアドレスの値と同じ値である。
なので、子サイド(呼び出し先)の関数のポインタにあるアドレス値をつかって、親サイド(呼び出し元)の引数のアドレスを操作できる。


このようにして、アドレスやポインタを使うことにより、子サイド(呼び出し先)の関数内で操作をした場合に、親サイドも含む範囲(子サイドの関数外)にも影響を与える非局所的な操作をすることができる。

「サブルーチン」との違い[編集]

C言語の関数のような「ローカル変数」のシステムは、よそのプログラム言語(ベーシックなど)の「サブルーチン」には無いシステムです。ベーシックのプログラム言語に、C言語でいう「ポインタ」システムが無いのは、そもそもローカル変数のシステムがベーシックには無いからです。

このC言語の「ローカル変数」システムこそが、C言語の「関数」システムの真骨頂(しんこっちょう)なのです。

通常仕様のベーシックでサブルーチンを呼び出しても、けっして、ルーチン内で使用する変数に呼び出し前の変数とは別アドレスは与えられませんし、そもそもサブルーチン呼び出し時に「引数」のコピーをしませんし、そもそもサブルーチン呼び出しに「引数」は不要ですし、そもそも「引数」のような概念じたいベーシックのサブルーチンの仕様には無いのです。

ベーシックでのサブルーチンの呼び出しは、単に「GOTO 100」みたいにGOTO文で行指定して指定先にジャンプするかわりに、行番号100にルーチン名を書いておいて、ルーチン名でジャンプ先を指定できるようにしたものに過ぎません。

しかし、C言語では、そもそもGOTO文のようなジャンプ方法が、忌避(きひ)されています。(いちおう、C言語にもGOTO文が存在するが、非推奨である)


裏を返せば、C言語で、BASICサブルーチンのような引数もなく「ローカル変数」性もない処理ブロックのあつまりに、C言語で名前を与えようとしても(BASICではそれぞれのサブルーチンに名前をつける事ができるし、そもそもサブルーチンの呼び出し時に名前で呼び出し先を指定する)、そのような構文はC言語には無いのです。

例えるなら、C言語でいう「関数」とは、独立国の国王が領民のために国土を他国から独立して確保しているように、「関数」も領民である変数のためのアドレスを他関数から独立して確保している(正確にいうと、(必要に応じてアドレスを確保できる))、一国一城の主であり、国王さまなのです。


いっぽう、ベーシックでいうサブルーチンは、けっして一国一城の主ではなく、全員、部下です、手下です、サラリーマンです。では、ベーシックの「サブルーチン」は誰の配下かというと、「行番号」さん の手下です。ベーシックのサブルーチンは行番号さんの手下ですので、けっして独立して変数アドレスを持つ事は許されず、そのため、変数のアドレスはべーシックのそのプログラム全体で共有することになるので、そもそも「ローカル変数」というシステムが、ベーシックには無いのです。

C言語で、もし、「main関数の変数アドレスをすべて共有したサブルーチン的なものの処理単位の使用を宣言したいな〜」と思っても、そのような処理単位はC言語の文法には存在しない。一見すると、「main関数の変数アドレスをすべて共有したサブルーチン」は便利そうに思えるかもしれないが、しかし、C言語の開発者はおそらく「このような処理単位は、main関数の変数を誤って書き換えてしまい誤操作につながるだろう」と考えたのだろうか、ともかくC言語にはこのような「変数アドレス共有サブルーチン」(いま勝手に考えた造語)は無いのである。


「メソッド」[編集]

「関数」というが、C言語の関数(function)は、数学の関数(function)とは、まったく違う。

これが、学習者の混乱の原因になるだろうとして、90年代ごろから、Visual C++などの言語では、C言語の関数とほぼ同じ意味で「メソッド」という用語が使われた。

Visual C++やC#などの言語でいう「メソッド」は、上述のC言語の「関数」と、ほぼ同じ機能、もしくは機能拡張したものである。


さらに2010年ごろから、function(ファンクション)に発音の似ている「アクション」の用語が一部のプログラム言語で使用されているが、しかし普及してない。


static 変数とポインタの必要性[編集]

このように、C言語の(ユーザーが自分で定義する)「関数」は、かなり特殊なシステムである。


// static変数の使用例(コンパイルできる)
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。

void kansuu(void) {
	int b = 0;
	static int c = 0;
	b = b + 1;
	c = c + 1;

	printf("b = %d ,c = %d \n\n" , b,c);
}

int main(void) {
	kansuu();
	kansuu();
	
	system("pause");// 「続行するには何かキーを押してください . . .」の待機命令

	return 0;
}
実行結果
b = 1 ,c = 1
b = 1 ,c = 2
続行するには何かキーを押してください . . .

のようになる。

このように、static宣言された変数(上の例ではc)は、その関数が終わっても、数値が保存される。

また、初期化がされる(cに0が代入される)のは、最初に呼び出された1回だけである。

いったい何が「静か」(static)なのか不明であるが、おそらく、関数の再度の呼び出し時にも変数を初期化しないままでいるという意味で、「静か」なのだろう。


なお、下記のように書くと、エラーになる

// static宣言でエラーになる例1
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。

void kansuu(void) {
	int b = 0;
	static int c = 0;
	b = b + 1;
	c = c + 1;
}

int main(void) {

	kansuu();
	printf("b = %d ,c = %d \n\n" , b, c); // このprintf関数がstatic宣言されたcにアクセスできない

	kansuu();
	printf("b = %d ,c = %d \n\n" , b, c); // このprintf関数がstatic宣言されたcにアクセスできない
	
	system("pause");// 「続行するには何かキーを押してください . . .」の待機命令

	return 0;
}

このように、static変数は、呼び出し元の関数は、存在を感知できない仕組みである。これによって、バグ発生時に原因箇所を特定しやすくしている。


また、以下のコードも、エラーになる。

// static宣言でエラーになる例2
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。

void kansuu(void) {
	b = b + 1; // ここでエラー発生。bが何かを判断できない
	c = c + 1;
}

int main(void) {
	int b = 0; // main関数で定義を行った場合
	static int c = 0;

	kansuu();
	printf("b = %d ,c = %d \n\n", b, c); // このprintf関数がstatic宣言されたcにアクセスできない

	kansuu();
	printf("b = %d ,c = %d \n\n", b, c); // このprintf関数がstatic宣言されたcにアクセスできない

	system("pause");// 「続行するには何かキーを押してください . . .」の待機命令

	return 0;
}

コンパイルの仕組みでは、上から順に部品を組み立てていくような仕組みなので、コード上方にある関数はコード下方にある関数の内容を知らない。


たとえ、コード下にある関数がmain関数であっても、その内容を知らないのである。よって、上記のようなコードはコンパイルエラーになる。

かといって、kansuuをmain関数よりも下に書くと、今度はmain関数が、kansuuの内容を感知できないのである。

main関数といえども、main関数ブロックよりも下に書かれた内容を知ることはできない。

なので通常、main関数は一番下に書かれる。


なお、最初の「例1」で紹介した例では、main関数で使用される変数bとcの上にある関数kansuuで定義されてるので、順序的には問題ないのですが、しかしC言語では、アクセスできないようになっています。staticかどうかに関わらず、アクセスできません。kansuuの終了時に、その関数の型などの情報も消えるので、エラー例1のコードでは変数bとcの宣言内容を発見できず、利用できない。


結局、冒頭に書いたコンパイル成功例のような書き方しか、ありません。


なお、static宣言した変数を、他の関数とも共有したい場合には、さらに「グローバル変数」という物を使います。

すべての関数ブロックの外の、コード前半部の部分(グローバル領域)で、変数宣言をすると、その変数は、すべての関数からアクセスできます。


実用では、子サイド(呼び出し先)の関数で、親サイド(呼び出しもと)の変数の値も変更したい場合も多く、その場合には、「グローバル変数としての static 変数」(静的変数)もしくは「ポインタ」という機能をつかう。

グローバル領域でのstatic 変数は、その名前の変数は、どこの変数で呼び出されても、かつ共通のアドレスで管理しているので、呼び出した(子サイドの)関数の先で、親サイド(呼び出し元の)関数も書き換えできる機能をもつ。


さて、C言語の入門書での「関数」の章の前半に書いてあるプログラムで、static変数やグローバル変数やポインタを使わなくても利用できる(ユーザー定義)関数は、むしろ珍しいプログラムなのである。画面に「こんにちは!」などの文字などを表示するだけなら、static変数などを使わなくても、そのようなアルゴリズムを構築できるので、そのようなプログラムがC言語の入門書の『関数』の章に書かれるのであろう。

なお、「return a;」などのように戻り値をつかって 計算結果の数値を 親(呼び出しもと)の関数に送信したりする方法は、じつは、あまり実用的ではなく、現実には不便な場合が多いのである。

なぜなら、

まず、親(呼び出しもと)の どの変数に値を送るかを、子(呼び出し先)のreturn文からは指定できないし、(けっして、親(呼び出しもと)の変数aなどを指定して、数値を代入してくれるわけではない)
しかも、その関数が終了してしまうので、ほかにも処理をその関数で続行したい場合に不便であるし、
しかも、1つの数値しか送れない、

など、いろんな欠点がある。

また、returnはエラーの有無を判定結果を呼び出しもと関数に送信するときに使用することが、実務では多い。たとえば if文などで、想定外の事態がなければ0を返却するようにプログラムを記述して、いっぽう想定外があれば-1を返却する、などのように、返却値の値により、想定外の有無を送信することが、実務などで多い。

C言語の入門書で、「return a+b;」などのように、return文で計算結果を送信する記述が書かれてるのは、初心者の学習のための配慮(はいりょ)なのであり、じつは、あまり実務的ではないプログラムである。そもそも、わざわざ別関数で「a+b」を計算せずとも、最初からmain関数内で「a+b」を計算すれば済む。

また、入門書で、ポインタの章と関数の章が別々に書かれていることが多いのは、初心者のための配慮(はいりょ)なのであり、じつは、現実では、ポインタと関数を別々に考えるのは、あまり実用的ではないだろう。


ローカル変数とグローバル変数[編集]

//例 グローバル変数とローカル変数

int g;//gはグローバル変数

int main(void)
{
	int l;//lはローカル変数
}

C言語では通常、変数をある関数ブロックの内部で宣言した際、その変数は、その関数ブロックの中でしか通用しない。 ある関数ブロックの内側でしか通用しない変数をローカル変数と呼ぶ。いっぽう、(プログラム冒頭部(include文の直後など)などの)関数外で宣言した変数をグローバル変数と呼び、すべてのブロック内で通用する。

変数は宣言する場所によってローカル変数とグローバル変数とに分かれる。[2]

ともかく、関数ブロック内で宣言した変数は、原則的にすべてローカル変数になるので、その変数はその関数の中でしか通用しない。

ローカル変数の存在意義は、(誤って他のブロック内の変数を書き換えられてしまうような)バグを未然に防ぐためである。このように変数の仕組みを、その変数を宣言したブロック内でしか通用しないという仕組みにすることにより、他ブロックを書き換えてしまうようなバグを未然に防ぐ・・・というのがローカル変数の意義である。


たとえ、他の関数ブロックで同名の変数が使用されていようが、それぞれの関数ブロックごとに、変数のアドレスは別々である。つまり、人間に例えるなら、同姓同名の別人のようなものである。

なので、通常、ある関数ブロックの中で宣言した変数は、すべてローカル変数である。たとえmain関数内で変数を定義をしても、その(main関数ないで宣言された)変数ですら、main関数内でしか通用しない。

もし、ある関数が、他の関数と値のやりとりをしたい場合は、関数の返却値(「返し値」、「戻り値」ともいう)を仲介して、値を別関数ブロックに送る必要がある。


ローカル変数はその定義により、性質として、その変数を宣言したブロックからのみアクセスでき、 他のブロックで宣言したローカル変数とは一切関係がない(ブロック有効範囲)。[3]

  • static

通常のローカル変数は、関数ブロックが終了するときに、変数の結果が消去される。そして、再度関数を呼び出したときは、変数の宣言時に変数が作成されたり、あるいは関数ブロックに入るときに作成され、そしてまた、その関数ブロックが終了するときに、消去される。

この消去により、ある関数が別の関数の変数を書き換えないような仕組みになっている。しかし、このような関数では、もし、ある関数を複数回呼び出しても、前回使用した計算結果が消去されてしまっており、そのため、前回の計算結果を再利用するような作業が困難である。

たとえば、高校数学で習うような数列の漸化式のような計算ですら、困難になってしまう。(数列の項を、関数に見立てている。)


そこで、変数を再利用可能にすることのできる static スタティックという宣言がある。このstaticをつけると、たとえローカル変数でも、関数の終了時に、その変数の値が消去されずにメモリに残りつづけるようになるので、再度その関数を呼び出したときに、前回の(そのstaticのついた変数の)計算結果を再利用できる。

「static」とは「静的」などと訳され、表現はいかめしいが、しかしstaticの実態は「関数が終わっても値を保管しとけ」程度の実態でしかない。


記憶域クラス指定子staticを持たないローカル変数は、 ブロックに入るときに作成され、 ブロックが終了するときに破壊される(自動記憶域期間)。[4]


つまり、そのブロックを含む同じ関数が再び呼び出されても、staticをつけてないかぎり、 前の値は保存されていない。

また、初期化していないローカル変数の値は不定であり、その値をそのまま「参照」してはならない。 (値を代入した後になら「参照」してもよい。)

なお、「参照」という言葉の意味が、C言語では、日常用語とは微妙に違います。C言語でいう「参照」とは、日常語でいう「内容確認」みたいな意味のほかにも、「ブロック外にある変数への、遠隔アクセスと(変数書き換え などの)遠隔操作」みたいな意味があります。「いま居る関数ブロックの場所とは別の、他の関数ブロック内で宣言された変数について、その他ブロックの関数内で定義された変数に与えられたアドレス(変数の宣言時に自動的にアドレス番号が割り振られる)に格納された値の内容にアクセスし、必要ならば、遠隔操作で(= いま居る関数ブロックにいるままで)他ブロックの変数を書き換える」というような意味です。
もし、他ブロックの変数書き換えをしないで、単にアドレス内容を確認するためにアクセスした場合だけなら、日常語でいう「参照」と同じ意味になるわけです。


  • グローバル変数との違い

すべてのブロックからアクセスできる変数を「グローバル変数」と呼ぶ。

C言語プログラミングでは一般に、コードファイルの冒頭の、どこの関数ブロック内でもない場所で変数宣言をすることにより、グローバル変数として宣言されたことになり、どこの関数からでもアクセスできる。

しかし、その変数にアクセスできることと、各関数ブロックの実行時に領域を新規作成しないかは別のことである。

なので、グローバル変数といえども、各関数ブロックの計算結果を共有したい場合には、さらに宣言時に static を接頭辞につけて宣言する必要がある。


グローバル変数は全てのブロックからアクセスできる(ファイル有効範囲)。[3] グローバル変数はプログラムが開始されたときに作成され、 プログラムが終了したときに破壊される(静的記憶域期間)。[4] また、グローバル変数は、定数式でのみ初期化することができ、 明示的に初期化しない場合、0で初期化される。

注意!
グローバル変数の乱用は避けるべきです。

理由は、どこからでもアクセスできることでバグの発見が困難になったり、
プログラムの実行中ずっと存在することでメモリの使用効率が落ちたりするからです。

なので、他の関数と計算結果を共有する必要のない変数であれば、なるべく、個別の関数の内部で変数宣言をするのが良い。

いっぽう、他の関数と計算結果を共通したい場合、どうしてもグローバル変数を使わざるを得ない場合も多い。(もし、グローバル変数を用いないと、代わりにポインタを使わざるを得ない場合がある。)

これはつまり、そもそも、複数の関数どうしでの変数の共有は、なるべく、ひかえる必要がある、という事である。


  • ローカル変数の寿命

関数ブロックの内部で宣言された変数を「ローカル変数」と言う。

ローカル変数は、その関数が終わるときに消えるが、正確に言うと、そのローカル変数を宣言したブロックが終了するときに、そのローカル変数も消去される。[5]

ブロックとは、記号 { と } とで挟まれた処理単位である。

なので、関数内に、さらにブロックが入れ子になっていて、その入れ子内で宣言された変数は、その入れ子ブロックの終了時にその入れ子内にあった変数も消去される。

if文やfor文の{}もブロックと見なされるので、注意が必要である。[6]


関数原型[編集]

main関数から呼び出される別の関数は、main関数よりも前で、存在が宣言されていなければならない。

そのため、main関数以外の関数は、main関数よりも前で、宣言されていなければならない。

しかし、main関数から呼び出される関数は、mainよりも前で宣言さえされていれば、たとえ具体的な定義内容がmain関数のあとで書かれていても、その関数をmain関数から呼び出して使用できる。そのため、以下のように、main関数から呼び出される関数の存在の宣言だけを先に行うという記法が許されており、この記法では、呼び出し先の関数の具体的な定義内容はmain関数のあとで定義する事が許されている。

このような関数の宣言方法のことを「関数のプロトタイプ宣言」などという。

(関数のプロトタイプ宣言の例)

//例 関数原型の使用例
#include <stdio.h>

double function(double a, double b);//関数原型

int main(void)
{
	printf("%f", function(0.12, 3.45));
}

//2つの浮動小数点数の和を求めるfunction関数
//関数原型があるおかげで、
//関数の使用の後に関数の定義を書くことができる。
double function(double a, double b)
{
	return a+b;
}

関数原型(関数プロトタイプ)とは、関数の宣言である。 ある関数が使われる前に、その関数名、引数、返却値を示し、その関数が使えるようにする。 関数原型を使うと、引数の個数とデータ型がチェックされる。

関数原型を使わなくても、関数が使われる前に、その関数の定義を書くことで、その関数を使うことができる。 だが、ソースコードが大規模になるとそれも難しくなる。 また、関数原型を使わず、関数が使われる前にその関数の定義を書かなかった場合、 返却値は暗黙のうちint型とされ、引数のデータ型もチェックされない。

関数原型の記述は次のようになっている。

返却値のデータ型 関数名(引数のリスト);

引数のリストの記述は次のようになっている。

引数のデータ型 引数名

引数のリストには、引数がない場合はvoidのみを書く。 複数の引数がある場合、コンマで区切る。

関数原型の引数の有効範囲は、関数宣言子の最後で終了する(関数原型有効範囲)。 [7]


static変数を使う[編集]

(※ 未記述)

上述したとおり。

ポインタを使う方法[編集]

ポインタを使う方法である。しかし、ポインタを使えば、2つ以上の数値を操作する事ができるので、ポインタを使って、親(呼び出し元)関数の複数の値を操作する事ができるので、それを「複数の返却値である」と解釈する事もできる

このような事を、「ポインタによる参照渡しの引数を、返却値として用いる」などと言ったりするが、要するにポインタを使って親(呼び出し元)の変数の値を操作するというだけの事であり、あまり深い意味はない。

//例 ポインタを使って関数に複数の返却値を持たせる。
#include <stdio.h>

int function(int *a, int *b)
{
	*a=1234;
	*b=5678;
}

int main(void)
{
	int i,j;
	function(&i, &j);
	printf("iの値は%d、jの値は%d。", i, j);
}

構造体を使う方法[編集]

別の方法として、返却値のデータ型に構造体を使って、 その構造体で複数の返却値を表現する方法もある。

#include <stdio.h>
#include <string.h>

//例 構造体を使って関数に複数の返却値を持たせる。
typedef struct 
{
	int i;
	double d;
	char c;
	char str[32];
}sKouzoutai;

sKouzoutai function()
{
	sKouzoutai ret;
	ret.i = 1234;
	ret.d = 3.14;
	ret.c = 'a';
	strcpy(ret.str, "Hellol. World!");
	return ret;
}

int main(void)
{
	sKouzoutai kouzoutai;
	kouzoutai = function();
	printf("kouzoutaiのメンバの値は、%d %f %c %sです。", kouzoutai.i, kouzoutai.d, kouzoutai.c, kouzoutai.str);
}

しかし、関数の返却値は、エラー判別などに利用される場合も多いので(アプリによって「エラーなら返却値に -1 を返却する」などの仕様のある場合も多い)、よって上述の手法は、あまりオススメできない。

なお、strcpyは、文字列をコピーするための命令。strcpyを使うには、string.hのインクルードが必要である。

値渡しと参照渡し[編集]

aとbとの値を入れ替えるswap関数を考える。

//例 誤ったswap関数
#include "stdio.h"

void swap(int a, int b){
	int x;
	x=a;
	a=b;
	b=x;
}

int main(){
	int a=1, b=2;
	printf("swap前のa=%d, b=%d\n", a, b);
	swap(a, b);
	printf("swap後のa=%d, b=%d\n", a, b);
}

なお、子(呼び出し先)の void swap(int a, int b) の a, b の部分を「仮引数」と呼ぶ。 またなお、親(呼び出し元)の swap(a, b); の a, b の部分を「実引数」と呼ぶ。

この例は意図通りに動作しない。何故なら実引数の値が仮引数にコピーされたためである。

swap前のa=1, b=2
swap後のa=1, b=2

と表示される。

//例 正しいswap関数
#include "stdio.h"

void swap(int *a, int *b){
	int x;
	x=*a;
	*a=*b;
	*b=x;
}

int main(){
	int a=1, b=2;
	printf("swap前のa=%d, b=%d\n", a, b);
	swap(&a, &b);
	printf("swap後のa=%d, b=%d\n", a, b);
}

この例は意図通りに動作する。何故なら、実引数のアドレスが仮引数にコピーされたためである。

swap前のa=1, b=2
swap後のa=2, b=1

と表示される。

例6のように実引数の値を仮引数にコピーすることを値渡しと呼ぶ。 例7のように実引数のアドレスを仮引数にコピーすることを参照渡しと呼ぶ。 [8]


関数の引数として配列を渡す[編集]

関数の引数として配列を渡すには、 先頭要素へのポインタを渡せばよい。 [9]

//例 関数の引数として配列を渡す
#include <stdio.h>

int sum(int *array, int size)
{
	int s=0;
	for(int i=0; i<size; ++i)
		s+=array[i];
	return s;
}

int main(void)
{
	int a[10]={2,3,5,7,11,13,17,19,23,29};
	int s=0;
	s=sum(a, 10);	

	printf("%d\n", s);
}
  1. ^ 矢沢久雄、原田英生『日経 BP パソコンベストムック C言語とC++がわかる本』日経ソフトウェア、2013年5月15日発行、096項
  2. ^ ローカル変数とグローバル変数という用語はよく使われるが、『JISX3010:2003』には出てこない。
  3. ^ 3.0 3.1 『JISX3010:2003』p.21「6.2.1 識別子の有効範囲」
  4. ^ 4.0 4.1 『JISX3010:2003』p.24「6.2.4 オブジェクトの記憶域期間」
  5. ^ ウェブページ『関数内で寿命が尽きる変数 』 http://9cguide.appspot.com/12-01.html
  6. ^ ウェブページ『ロベールのC++教室 - 第68章 寿命 -』 http://www7b.biglobe.ne.jp/~robe/cpphtml/html01/cpp01068.html
  7. ^ 『JISX3010:2003』p.21「6.2.1 識別子の有効範囲」
  8. ^ 値渡しと参照渡しという用語はよく使われるが、『JISX3010:2003』には出てこない。
  9. ^ 前橋和弥著『C言語 ポインタ完全制覇』p.068 平成13年6月25日初版第4刷発行

参考文献[編集]

  • 日本工業標準調査会『JISX3010 プログラム言語C』2003年12月20日改正