C言語のプログラムを学び始めると、必ず最初に出てくるのがmain関数です。
見よう見まねでint main(void)と書いている方も多いですが、なぜこの形なのか、戻り値にはどのような意味があるのかを理解していると、エラー調査や実践的な開発で大きな差が出ます。
本記事では、main関数の役割から書き方、終了コードの扱いまで、初学者にも分かるように体系的に解説します。
C言語のmain関数とは
main関数の基本的な役割

main関数は、C言語プログラムにおけるエントリポイント(開始地点)です。
OSまたは実行環境は、コンパイルして生成された実行ファイルを起動すると、内部的に必ずmain関数を呼び出します。
Cプログラムの中には、標準ライブラリ関数や自分で定義した関数など、さまざまな関数が存在しますが、それらは最終的にmain関数から呼び出されることを前提に設計されます。
main関数は、プログラムの流れを制御する「司令塔」のような役割を持ちます。
また、main関数はプログラムの終了コード(終了ステータス)をOSに返す役割も担います。
この終了コードによって、OSやほかのプログラムは、そのプログラムが正常終了したのか、エラーが発生したのかを判断できます。
main関数がプログラムの開始点になる理由

C言語のソースコードそのものには「この関数から必ず実行を始めなければならない」という記述はありません。
しかし、コンパイラとOSの間で「Cプログラムはmainから開始する」という取り決めがあり、それに従って実行が始まります。
実行の大まかな流れは次のようになります。
OSが実行ファイルを起動すると、まずランタイムと呼ばれる初期化処理が行われます。
グローバル変数や静的変数の初期化、標準入出力ストリームの準備などがここで実施され、その後にランタイムからmain関数が呼び出されます。
このように、プログラマが意識しないところで多くの準備が行われた結果、main関数の先頭行からプログラムの「本当の処理」が始まるのです。
C言語規格におけるmain関数の位置づけ

C言語は、国際規格(ISO/IEC)によって仕様が定められています。
このC言語規格において、main関数は特別な関数として位置付けられています。
主なポイントを整理すると、次のようになります。
- Cプログラムは翻訳単位全体として1つのmain関数を持たなければならない
- main関数の戻り値の型はint型でなければならない
- main関数のパラメータの形には、いくつかの規格で認められた書式がある
- main関数の戻り値は、プログラムの終了ステータスとしてOSに渡される
このように、main関数は言語仕様のレベルで特別扱いされており、「必ず書くべき関数」であると同時に「形を守るべき関数」でもあります。
main関数の正しい書き方
代表的なmain関数の書式

C言語の規格で認められているmain関数の代表的な形は、次の2通りです。
- 引数なしのmain関数
int main(void) - コマンドライン引数付きのmain関数
int main(int argc, char *argv[])
または同義のint main(int argc, char **argv)
いずれの場合も、戻り値の型はintであることが重要です。
これ以外の戻り値の型(例えばvoid main())は、標準Cの規格上は未定義または未規定の動作となり、移植性のあるコードにはなりません。
引数なしのmain関数の書き方

もっとも基本的な形として、コマンドライン引数を使わない引数なしのmain関数があります。
#include <stdio.h> // printfを使うために必要なヘッダ
// 引数なしのmain関数
int main(void)
{
// ここにプログラムのメイン処理を書きます
printf("Hello, C world!\n");
// 戻り値0は「正常終了」を意味します
return 0;
}
Hello, C world!
ここでのポイントは、引数の部分をvoidと書くことです。
C言語では「引数がない」ことを明示するにはvoidを指定するのが正しい書き方です。
int main()のように何も書かない形は、古いC(旧K&Rスタイル)では「引数の個数・型が不定」という意味になり、モダンなCでは好ましくありません。
明示的にvoidを書いて「引数を受け取らない」ことをはっきりさせるのがベストです。
コマンドライン引数付きmain(int argc, char *argv[])の書き方

コマンドラインから引数を受け取るプログラムでは、次のようにパラメータ付きのmainを使います。
#include <stdio.h>
// コマンドライン引数を受け取るmain関数
// argc: 引数の個数
// argv: 引数文字列へのポインタ配列
int main(int argc, char *argv[])
{
int i;
// 引数の個数を表示
printf("引数の個数: %d\n", argc);
// 各引数を順に表示
for (i = 0; i < argc; i++) {
// argv[i] は i番目の引数を表す文字列
printf("argv[%d] = %s\n", i, argv[i]);
}
// 正常終了
return 0;
}
$ ./a.out hello 123
引数の個数: 3
argv[0] = ./a.out
argv[1] = hello
argv[2] = 123
このときのargcとargvの意味は以下の通りです。
- argc(int型)
コマンドライン引数の個数を表します。プログラム名そのものも1個として数えられるため、./a.out helloであればargc == 2になります。 - argv(char *argv[] または char **argv)
各引数を表す文字列(ヌル終端文字列)へのポインタを格納した配列です。argv[0]にはプログラム名、argv[1]以降に実際の引数が入ります。
また、規格上はargv[argc]がNULLであることが保証されており、これを使うことで、argcを使わずに引数列の終端を調べることも可能です。
#include <stdio.h>
int main(int argc, char *argv[])
{
// argcを使わずにargv[i]がNULLになるまでループする例
int i = 0;
while (argv[i] != NULL) {
printf("argv[%d] = %s\n", i, argv[i]);
i++;
}
return 0;
}
$ ./a.out A B
argv[0] = ./a.out
argv[1] = A
argv[2] = B
main関数で使用できるデータ型と注意点

C言語規格では、main関数の戻り値の型は必ずintであることが求められます。
戻り値の型をvoidやfloatなどにするのは規格違反であり、移植性のないコードになります。
一方で、引数の型や名前については、ある程度の幅が認められています。
- 戻り値は
intで固定 - 引数なしのときは
voidを明示するのが推奨 - 引数ありのときは典型的に
int argc, char *argv[]を使う char **argvはchar *argv[]と同義であり、どちらも使用可能
標準規格では、環境によってはchar *envp[]など環境変数へのポインタ配列を渡す形も許容されていますが、多くの入門ではint main(void)かint main(int argc, char *argv[])の2つだけを知っていれば十分です。
main関数の戻り値と終了コード
戻り値intの意味と役割

main関数がint型を返すのは、プログラムの終了ステータスをOSに伝えるためです。
一般的な慣習として、
- 0 … 正常終了
- 0以外 … なんらかの異常・エラー終了
という意味になります。
たとえば、LinuxやmacOSのシェルでは、直前に実行したプログラムの終了ステータスを$?で確認できます。
$ ./a.out
(プログラムの出力)
$ echo $?
0
この0が、main関数のreturnで返した値に対応しています。
return 0とreturn 1の違い

Cプログラムでは、return 0は「正常終了」を意味する慣習があります。
一方でreturn 1など、0以外の値は「何らかのエラーがあった」ことを表します。
簡単な例を見てみます。
#include <stdio.h>
#include <stdlib.h> // atoiを使うため
// コマンドライン引数で与えられた数値が偶数なら0、奇数なら1を返す例
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "使い方: %s number\n", argv[0]);
// 引数不足のエラーとして1を返す
return 1;
}
int n = atoi(argv[1]);
if (n % 2 == 0) {
printf("%d は偶数です。\n", n);
return 0; // 正常終了(偶数だった)
} else {
printf("%d は奇数です。\n", n);
return 1; // 条件を満たさない(ここでは奇数)ことを示す例
}
}
$ ./a.out 4
4 は偶数です。
$ echo $?
0
$ ./a.out 5
5 は奇数です。
$ echo $?
1
$ ./a.out
使い方: ./a.out number
$ echo $?
1
このように、用途に応じて0以外の値に意味を持たせることで、シェルスクリプトや他のプログラムからエラーの内容を判別しやすくできます。
慣習として0は成功、1以上の整数でエラー種別を表すように設計することが多いです。
exit関数とmain関数の戻り値の関係

C標準ライブラリにはexit関数が用意されており、これを使ってプログラムを途中で終了させることができます。
#include <stdio.h>
#include <stdlib.h> // exit関数の宣言がある
int main(void)
{
printf("処理1\n");
// 何らかの致命的なエラーが発生したと仮定
fprintf(stderr, "致命的なエラーが発生しました。\n");
// エラーコード1を指定してプログラムを終了
exit(1);
// ここには絶対に到達しません
printf("この行は実行されません。\n");
return 0; // 到達不能
}
処理1
致命的なエラーが発生しました。
exit関数に渡した整数値は、そのまま終了ステータスとしてOSに返されます。
したがって、exit(0)はreturn 0;とほぼ同じ意味になります。
違いとして、exit関数は内部で
- 標準入出力バッファのフラッシュ
atexitで登録された関数の呼び出し- 一部のリソース解放処理
などを行いますが、mainからのreturnでも同様の終了処理が実行されるように、処理系側で設計されています。
通常のプログラムでは、mainの末尾でreturnを使う形がシンプルで推奨されます。
ただし、関数の深い呼び出し階層の中から一気にプログラム全体を終了させたい場合には、exit関数を利用することがあります。
OSから見た終了ステータスの扱い

OS(特にUnix系)では、終了ステータスはプロセス間連携の基本手段として扱われます。
シェルスクリプトや他のプログラムは、子プロセスとしてCプログラムを起動し、その終了コードに応じて処理を分岐できます。
簡単なシェルスクリプトの例を文章で説明すると、次のような使い方が典型的です。
- Cプログラムが0を返したら「成功」とみなし、次の処理へ進む
- 0以外だったら「エラー」とみなして、エラーログを出したり、処理を中断したりする
このように、main関数の戻り値は自分のプログラム内部の話にとどまらず、周辺のシステム全体と連携するためのインタフェースでもあります。
単にreturn 0;と書くだけでなく、エラーの種類に応じた終了コードを設計すると、後から扱いやすいプログラムになります。
main関数でよくある疑問と注意点
main関数を省略できるか

C言語のプログラムでは、main関数を省略することはできません。
main関数はエントリポイントとして規格上特別扱いされており、これがないとプログラムの「開始地点」が存在しないことになるからです。
実際にmain関数を定義しないでコンパイルすると、多くの場合、リンクの段階で次のようなエラーが発生します。
undefined reference to `main'entry point main not defined
など、実装依存ですが趣旨は「mainが見つからない」という内容です。
教育環境や一部の組み込み向けツールでは、特殊な拡張としてmainを書かなくても動く場合がありますが、それらは標準Cではなく、特定処理系に依存した仕様です。
学習や実務では必ずmain関数を定義すると覚えておくことが重要です。
main関数を複数定義した場合のエラー

1つのプログラムにmain関数は1つだけというのも重要なルールです。
複数のソースファイルをリンクして1つの実行ファイルを作成する場合でも、最終的なプログラム全体としてmainは1つだけでなければなりません。
もし複数のソースファイルにmain関数を定義してリンクすると、多くの処理系で次のようなエラーになります。
multiple definition of `main'linker error: entry point defined multiple times
この問題を避けるには、main関数を置くファイルを1つだけに決め、その他の機能は別の関数として定義し、mainから呼び出す構造にします。
例として、ファイル構成を以下のように分けます。
main.c… main関数だけを定義して、全体の流れを制御calc.c… 計算処理の関数を定義io.c… 入出力処理の関数を定義
このように分割すると、プロジェクト全体の見通しも良くなります。
void mainはなぜ推奨されないか

インターネット上の古い資料や、組み込み向けのサンプルコードの中には、void main(void)という形が出てくることがあります。
しかし、標準Cの規格ではmain関数の戻り値はintであると定められており、void mainは規格に反します。
void mainがなぜ問題かを整理すると、次のようになります。
- 終了ステータスをOSに返せないため、システム側から見て「成功か失敗か」が分からない
- 一部のコンパイラではコンパイルできても、他の環境ではエラーや未定義動作になり得る
- 規格に準拠したコードを書いておかないと、将来的な移植や保守で問題が出やすい
そのため、現代的なCプログラミングではvoid mainは使用しないのがベストプラクティスです。
必ずint main(void)またはint main(int argc, char *argv[])の形を用います。
main関数を書くときのベストプラクティス

最後に、main関数を書くときの実践的なベストプラクティスをいくつかまとめます。
1. 戻り値は必ずintにして、適切な終了コードを返す
int main(...)とし、正常終了時はreturn 0;、エラー時は目的に応じた0以外の値を返すようにします。
これにより、スクリプトや他のプログラムから利用しやすくなります。
2. main関数はできるだけ短く保つ main関数にロジックを詰め込みすぎると読みづらくなります。
次のように、大まかな流れだけをmainに書き、詳細処理は別関数に分離するのが理想です。
#include <stdio.h>
// プログラムの実際の処理を担当する関数
// エラー時は0以外を返す設計にしておく
int run_application(void)
{
// 実際の処理を書く
printf("アプリケーションの処理を実行中...\n");
// ここでは正常終了とする
return 0;
}
// main関数は初期化と終了コードの受け渡しに専念させる
int main(void)
{
int result;
// 必要があれば、ここで初期化処理を行う
// 実際の処理を別関数に委譲
result = run_application();
// run_applicationの戻り値をそのままOSに返す
return result;
}
アプリケーションの処理を実行中...
3. 引数なしのときはvoidを明示する
int main()ではなくint main(void)と書き、「引数は受け取らない」ことを明確にすると、可読性と移植性が向上します。
4. コマンドライン引数を使う場合はargc/argvを丁寧に扱う
argcの値を必ずチェックし、想定より少ない場合は使い方を表示して終了するなど、防御的なプログラミングを心掛けます。
まとめ
main関数は、Cプログラムの開始点であると同時に、OSへ終了ステータスを返す重要なインタフェースです。
戻り値は必ずintとし、引数の有無に応じてint main(void)やint main(int argc, char *argv[])と書くのが標準的な形になります。
void mainなどの非標準的な書き方は避け、mainを短く保って処理は別関数に委譲することで、読みやすく保守しやすいプログラムを構築できます。
規格に沿ったmain関数の設計を身につけておくことが、C言語を実務レベルで扱ううえでの大きな基盤となります。
