main関数の戻り値 🔗
main関数の戻り値の型は原則として int型です。ほかの型が使える可能性はありますが、それは処理系定義📘です[2]。
main関数にかぎっては、戻り値の型が void でなくても return文を省略でき、関数の末尾に return 0;
があるかのように扱われます[3]。
【上級】main関数の戻り値が int型の場合に、main関数を終了することは、戻り値を実引数として exit関数を呼び出すことと同じ意味になります。[4]
このページの解説は C99 をベースとしています。
以下は目次です。
これまでに、main関数、puts関数、fgets関数といった関数📘が登場していますが、ここまであまり深入りせずに進めてきました。この章では、もう少し詳細なところを説明をしていきます。
関数は、「ある1つの仕事をこなすために必要なソースコードを1か所にまとめて名前を付けたもの」という捉え方ができます。fgets関数は、「1行分の入力を文字列として受け取る」という仕事をまとめて、それを fgets という名前で表現しています。適切に名前を付けることは可読性の向上につながるので、これは関数を使う利点の1つといえます。
fgets という名前は、省略されすぎていてそれほど分かりやすくはないですが。ちなみに、file get string の略です。
また、何度か必要になる処理が1つの関数としてまとめられていれば、繰り返し同じコードを記述しなくて済みます。これはコードを再利用できるということで、開発効率を向上させる意味があります。また、その処理の一部を修正しなければならなくなったとき、関数の中身だけを直せばよく、プログラム内のあちこちを直してまわる必要がなくなります。これも関数の重要な利点です。
これまで、必須である main関数の中身を記述し、そこから puts関数などのほかの関数を呼び出して使っていました。自分で新たな関数を作成することもでき、C言語のプログラムはそうやって新たな関数を次々に作りながら組み立てていきます。
printf関数や fgets関数のように、処理系📘が用意しなければならないと標準規格で定められている関数がいくつもあります。このような関数を、標準ライブラリ関数 (standard library function) といいます。
標準ライブラリ関数を使うには、#include <stdio.h>
のような記述が必要になります。この記述は、インクルード (include) と呼ばれ、指定したファイルの内容を取り込むという意味があります。stdio.h の内容は、標準ライブラリ関数を使うために必要な、C言語で書かれたソースコードです。
stdio.h などのファイルは、開発環境📘をインストール📘したフォルダのどこかに実際に存在します。Visual Studio の場合なら、#include <stdio.h>
という記述のところを右クリックして、「ドキュメント <stdio.h> を開く」を選ぶと簡単に中身を確認できます。絶対にファイルの内容を書き換えないように注意してください。
どの標準ライブラリ関数が、どのファイルを必要とするのかは標準規格によって定められており、使いたい関数に応じて、正しいものを指定しなければなりません。よく使う関数は覚えてしまえばいいのですが、標準ライブラリ関数は非常にたくさんありますから、まれにしか使わない関数は、リファレンスで調べるなどしなくてはなりません。この件にかぎらず、調べる習慣や方法は身に付けてください。
標準ライブラリ関数の詳細については、「標準ライブラリのリファレンス」もご利用ください。
標準ライブラリ関数の価値は、どの処理系でも同じ使い方ができて、同じ結果を得られるということです。標準ライブラリ関数を使うようにしてれば、作ったプログラムをさまざまな環境で使いまわせる可能性が高まります。また、非常にレベルの高いプログラマーが実装しているであろうことから性能も良く、多くの人々が実際に使っているという事実が不具合の少なさを保証しているともいえます。事情がない限り、標準ライブラリ関数でできる処理を自力で書くべきではありません。そして、そのためには標準ライブラリ関数のことを積極的に知ろうとする、なにか使えるものがないか調べようとする態度も必要です。
処理系によっては、標準規格には存在しない関数をいくつか追加で実装していることがあります。このような関数には、標準ライブラリ関数ではできないような、かなり便利な機能が含まれていることが多いのですが、当然、他の処理系では使えません。C言語編としては、標準でない関数については原則として取り上げていません。
新たな変数を作るときに宣言📘を記述するように、新たな関数を作るときにも、まずは関数の宣言を記述します。
あとで説明する「関数の定義」があれば、宣言を書かずに済ませられることがあります。
宣言とは、「こういう名前のものがある」ということをコンパイラ📘に伝える記述です。関数の宣言を記述することで、その名前の関数が存在するという事実を明示したことになります。標準ライブラリの関数を使うときに、#include <****>
のようなインクルードを書くのは、標準ライブラリ関数の宣言を取り込むためです。
関数の宣言は次の構文で記述します。
(仮引数); 戻り値の型 識別子
「戻り値の型」には、関数から返す値の型を記述します。“関数から返す値” のことを戻り値(返し値、返り値、返却値)📘 (return value) といいます。C言語では戻り値は 1個、あるいは 0個でなければならず、1個ならその値の型を、0個なら void と記述します。
int 識別子(仮引数); // int型の値を返す関数の宣言
void 識別子(仮引数); // 戻り値がない関数の宣言
「戻り値の型」を配列型にすることはできません。これは重大な制約ですが、回避する方法はあります。ただ、現時点の知識では難しいので、ここでは取り上げないことにします(第33章で取り上げます)。
「識別子」には、関数の名前を記述します。使える文字のルールは変数のときと同様です(「第4章」を参照)。
int get_score(仮引数);
関数に限りませんが、名前の付け方についていくつか流派のようなものがあって、代表的なものに、get_score
のように小文字を使い、_
で区切る規則で付ける方法(スネークケース📘 (snake case))と、GetScore
のように単語の頭を大文字にする規則で付ける方法(キャメルケース📘 (camel case))があります。標準ライブラリではスネークケースが用いられているので、当サイトではスネークケースを採用します(変数名でも同様の方針です)。
一緒に使うほかのライブラリが異なる方法を採用していると、結局どこかで混在することになりますが、自分たちで書く部分については統一しておこうとするのが一般的だと思います。
「仮引数📘 (parameter)」には、関数を呼び出す側から渡されてくる値を受け取る変数の型と名前を、,
で区切りながら書き並べます。仮引数が必要なければ void
と書きます。
int get_score(void); // 仮引数がない関数の宣言
int get_score(int player_no); // 仮引数が1つの関数の宣言
int get_winner_no(int player1_no, int player2_no); // 仮引数が2つの関数の宣言
「仮引数」の部分を空にして、int get_score();
のようにも書けますが、C99 ではこの記述と void
を記述することとでは意味が異なっており、勧められる方法でもありません[5]。
int get_score(); // C99 ではよくない方法。仮引数は「ない」のではなく「不明」
【C23】仮引数が空の場合を void
と記述した場合と同じとみなすことになりました。したがって C23以降では空にして問題ありません[6]。
戻り値と同じく、仮引数を配列型にすることはできませんが、回避する方法があります(第33章で取り上げます)。
関数の宣言に、関数の名前、仮引数の型と個数・順番が記述されていることによって、その関数を呼び出そうとしている箇所の記述が適切かどうかをコンパイラが判断できます。たとえば、仮引数がないのに、score = get_score(100);
のように呼び出すのは間違っているためコンパイルエラーにできます。また、仮引数が int型のところへ、12.3
のような型が異なる値を渡そうとしていることも検出できます。このようなコンパイラによるチェックに使える関数宣言のことを、関数プロトタイプ(関数原型) (function prototype) と呼びます。
しかし、C99 では「仮引数」が空になっている関数の宣言は関数プロトタイプとして機能しません[1]。そのため、引数を指定して呼び出そうとしてもコンパイラは許してしまいます。
【C23】前述のとおり、C23 からは仮引数が空の場合、void
を指定したときと同じとみなすようになりました。関数プロトタイプとして機能し、引数を指定した呼び出しをエラーであると判断できるようになっています[6]。
【C++プログラマー】C23 より前のC言語では、仮引数を空にした場合と void
と書いたときの意味は異なっており、空にした場合は実引数の型や個数をチェックしません。空にすることに利点はないので、必ず void と記述するべきです。C23 からは void
を指定することと同じになったので、空にして構いません。
仮引数が複数必要な場合は、記述する順序にも気を留めるようにしてください。関数を呼び出す側は、仮引数の順番に合わせて、値を書き並べなければならないので、あまりに不自然な順序であったり、関数ごとに一貫性が欠けていたりするのは嫌われます。
関数の宣言を記述した位置よりもうしろからでないと、その関数を呼び出すことはできません。インクルードをソースファイルの先頭付近に書くのはこのためです。
// 宣言(別のところに定義が必要)
void func(int value);
int main(void)
{
(100); // OK
func}
int main()
{
(100); // コンパイルエラー。宣言がみえない
func}
// 宣言(別のところに定義がある)
void func(int value);
関数の宣言には、肝心の「関数の中身」がありません。つまり、その関数が何をするのかが記述されていません。関数の中身を記述したものを、関数の定義📘 (definition) と呼びます。
関数の定義は、次の構文で記述します。
(仮引数)
戻り値の型 識別子{
処理}
このかたちはいつも書いている main関数のかたちと同じです。つまりこれまでのページで書いてきたプログラムは、main関数の定義を書いていたわけです。
最初の行は、末尾の ;
がないことを除いて、関数の宣言とまったく同じですし、同じでなければなりません。
【C23】「処理」のところで使用しないのであれば、仮引数の名前を省略できるようになりました。
{
と }
の内側に、関数がおこなう処理(関数の本体)を書きます。これまで main関数の内側に書いてきたコードが同じように書けます。たとえば、変数を宣言したり、ほかの関数を呼び出したりできます。関数の中で宣言した変数は、別の関数からは使えません。
関数が呼び出されると、呼び出し側で指定した値(f(100, n);
のように呼び出したとすれば 100 と n)が、対応する仮引数の初期値として渡されてきます。関数の本体では、その仮引数を使用できます。
// 宣言
void print_values(int a, int b);
// 定義
// 仮引数a、b は、呼び出し側から渡されてきた値で初期化される変数
void print_values(int a, int b)
{
("%d, %d\n", a, b);
printf}
「戻り値の型」が void の場合は、処理が末尾の }
のところまで実行されると、呼び出し元へ戻ります。あるいは、return;
という記述によっても呼び出し元へ戻ります。これは return文 (return statement) という文です。
「戻り値の型」が void でない場合は、return文が必須です(main関数だけは例外的に省略できる)。return文は、return 0;
とか return a;
といったふうに、戻り値を指定することができ、これが呼び出し元に返されます。
// 整数の入力を受け取る
// 戻り値: 標準入力から整数の入力を受け取り、その値を返す。
int get_input_integer(void)
{
char s[40];
(s, sizeof(s), stdin);
fgetsint value;
(s, "%d", &value);
sscanfreturn value; // value の値を戻り値として返す
}
戻り値には名前を付けられませんから、呼び出す側が、どんな意味の戻り値が返ってくるのか分かるようにすることが望まれます。1つには、関数名を分かりやすくする方法がありますが、限界もあるので、コメントを書き添えておくのが妥当な手段です。戻り値の意味のほか、引数の意味やルール、その関数がどんなことをするものなのかといったことを書きます。呼び出し側から確実にみえるところにあるのは関数の宣言のほうなので、これらのコメントは、関数の宣言のところに書きます(定義しかない場合は定義のところでもいい)。
関数を活用すると、プログラムの実行順は上から下へ進むというだけでなく、ほかの関数の内側に移動したり戻ってきたりもすることを理解しなければなりません。
Visual Studio のステップ実行の機能を使うなどして、実行の流れを確認してみるのもいいかもしれません。「Visual Studio編>ステップ実行」のページを参照してください。
関数の定義は、関数の宣言の代わりになります。つまり、その関数を呼び出そうとしている位置より手前に定義があれば、宣言を記述しなくても問題ありません。
// 定義(宣言はない)
void func(int value)
{
// ...
}
int main(void)
{
(100); // OK
func}
しかし、プログラムの複雑が大きくなってくると、宣言を記述しないとうまくコンパイルできない場面が出てくることがあります。基本的には、宣言は記述するものです。
関数は、その名前と ()
を使った、関数呼出し📘 (function call) と呼ばれる式によって呼び出せます。
(実引数) 関数名
()
の内側に関数に渡す値を書き並べます。この値のことを、実引数📘 (argument) と呼びます。仮引数と実引数を区別しなくていい場面では、いずれも単に引数📘 (parameter、argument) と呼ぶことがあります。
実引数は、関数の宣言や定義のところに書いた仮引数の順番に合わせて記述しなければなりません。型が一致しない場合はコンパイルエラー(警告の場合もある)になります。結果的に値になればいいので、f(a + b)
のような計算式を記述しても構いません。
当然、同じ関数を何度も呼び出すことができ、そのたびに実引数の値を変えることが可能です(これまでにも、printf関数や puts関数に渡す実引数はいつも違っていました)。そのため、うまく関数を作ることで、同じ仕事をするコードを1つにまとめられます。将来同じコードが必要になったときには、その関数を呼び出すだけで簡単に済みますし、同じコードが色々なところに散らばっていないので、後からコードを修正することも容易になります。
さきほど定義した get_input_integer関数を次のように呼び出して使用できます。
#include <stdio.h>
// 整数の入力を受け取る
// 戻り値: 標準入力から整数の入力を受け取り、その値を返す。
int get_input_integer(void)
{
char s[40];
(s, sizeof(s), stdin);
fgetsint value;
(s, "%d", &value);
sscanfreturn value; // value の値を戻り値として返す
}
int main(void)
{
("Please enter the integer.");
putsint value1 = get_input_integer();
("Please enter the integer.");
putsint value2 = get_input_integer();
("%d + %d = %d\n", value1, value2, value1 + value2);
printf}
実行結果:
Please enter the integer.
15 <-- 入力された内容
Please enter the integer.
7 <-- 入力された内容
15 + 7 = 22
関数の呼び出し元は、戻り値を必ずしも変数で受け取らないといけないわけではなく、返された値をほかの関数の実引数に使うといったことも可能です。
("%d\n", get_input_integer()); // 戻り値をほかの関数の実引数に使う printf
関数が戻り値を返すように実装されていても、呼び出し側はその戻り値が不要だというケースがあります。たとえば、printf関数にも戻り値はあって、正常に出力できた文字数を返すという仕様になっています(リファレンス参照)。ほとんどの場合、これを受け取っても使い道がないため無視しています。
しかし、戻り値を無視することが明らかにおかしいといえる関数もあります。たとえば、さきほど定義した get_input_integer関数は、整数の入力を受け取るという目的をもって使う関数なので、呼び出しておきながら、戻り値はいらないというケースはあり得なさそうです。しかし、戻り値を受け取らないという選択が許されている以上、次のようなコードが書けてしまい、バグの原因になるので注意が必要です。
int value;
(); // 戻り値を受け取るのを忘れている
get_input_integer("%d\n", value); // それでも変数value 自体は存在しているからアクセスできる (だが中身は不定値) printf
コンパイラが警告を出している場合があるので、しっかり確認するようにしましょう。このコードの場合、Visual Studio 2017 は、“warning C4700: 初期化されていないローカル変数 ‘value’ が使用されます” という警告を出しました。戻り値を受け取っていないという直接的な指摘ではありませんが、変数value に値が入らないまま使ってしまっていることを明確に教えてくれています。
main関数の戻り値の型は原則として int型です。ほかの型が使える可能性はありますが、それは処理系定義📘です[2]。
main関数にかぎっては、戻り値の型が void でなくても return文を省略でき、関数の末尾に return 0;
があるかのように扱われます[3]。
【上級】main関数の戻り値が int型の場合に、main関数を終了することは、戻り値を実引数として exit関数を呼び出すことと同じ意味になります。[4]
問題① 次のプログラムの実行結果はどうなりますか?
#include <stdio.h>
void f1(int n);
void f2(int n1, int n2);
int main(void)
{
(1);
f1(10, 20);
f2}
void f1(int n)
{
(n, n + 1);
f2}
void f2(int n1, int n2)
{
("%d, %d\n", n1, n2);
printf}
問題② int型で渡される引数の値を、2乗して返す関数を作ってください。
int main(void)
か int main(int argc, char *argv[])
、またはこれらのいずれかと等価な方法、あるいはそのほかの処理系定義の方法とある。前3つなら戻り値は int型ということになり、最後の1つの可能性として int以外の戻り値が許されるかもしれない
return 0;
を削除(C言語編全体でのコードの統一)
Programming Place Plus のトップページへ
はてなブックマーク に保存 | Pocket に保存 | Facebook でシェア |
X で ポスト/フォロー | LINE で送る | noteで書く |
![]() |
管理者情報 | プライバシーポリシー |