C言語を基礎から順に学びたい場合は、C言語 学習ロードマップ を参照してください。
この記事は補足です。先にポインタ入門を読むと、char *argv[]の見方が分かりやすくなります。
C言語のプログラムには、基本的に main 関数があります。
これまでの例では、次の形を使ってきました。
int main(void)
ただ、C言語のコードを読むようになると、次のような書き方も見かけます。
int main(int argc, char *argv[])
また、古いコードや特定の開発環境では、次の書き方を見かけることがあります。
void main(void)
これらは何が違うのでしょうか。
この記事では、main 関数が担う役割、int main(void) と int main(int argc, char *argv[]) の使い分け、そして void main を新しく書くべきではない理由を、順番に確認します。
この記事は、Windows 11 + WSL + GCC でC言語を学ぶことを前提にしています。
前提
この記事では、関数、文字列、配列、ポインタの基本を使います。
まだ読んでいない場合は、先に次の記事を読んでください。
この記事で分かること
-
main関数がプログラムの入口になる理由 -
int main(void)の意味 -
return 0;とreturn 1;の意味 -
void mainを新しく書かない方がよい理由 -
argcとargvの意味 - コマンドライン引数を受け取り、使い方を表示する方法
- 文字列で渡された引数を整数として安全寄りに扱う最初の方法
先に結論
普段のC言語学習では、次の2つを使い分ければ十分です。
// 起動時に値を受け取らないプログラム
int main(void)
// 起動時にターミナルから文字列を受け取るプログラム
int main(int argc, char *argv[])
void main(void) は、新しく書くコードでは使わないでください。
一部の処理系ではコンパイルや実行ができる場合がありますが、WSL + GCC で標準的なCとして学ぶなら、main の戻り値は int に統一します。
1. main 関数はプログラムの入口
C言語のプログラムは、基本的に main 関数から処理を始めます。
#include <stdio.h>
int main(void)
{
printf("プログラムを開始します。\n");
return 0;
}
実行すると、次の流れになります。
ターミナルから実行する
↓
C言語の実行環境がプログラムを起動する
↓
main 関数の先頭から処理が始まる
↓
return で main 関数が終わる
↓
プログラムが終了する
main は、ほかの関数と同じように見えます。
ただし、プログラムを起動した側から最初に呼ばれる特別な関数です。
main は自分で呼び出さない
通常の関数は、自分で呼び出します。
print_message();
一方、main は自分で呼び出しません。
// main(); のようには書きません。
ターミナルから実行したときに、実行環境が main 関数を呼び出します。
そのため、学習用のプログラムでは、処理の始まりを main 関数に書きます。
2. int main(void) の意味
これまで使ってきた形を、単語ごとに分けて見ます。
int main(void)
| 部分 | 意味 |
|---|---|
int |
main 関数が、終了時に整数を1つ返す |
main |
プログラムの入口となる特別な関数名 |
void |
この関数は、呼び出されるときに値を受け取らない |
つまり、次のように読めます。
値を受け取らずに始まり、
終了時に整数を返す main 関数
void は「何も返さない」ではない
int main(void) の void は、main の戻り値を表しているわけではありません。
void は、かっこの中にあるため、引数を受け取らないことを表します。
int main(void)
一方、先頭の int は、終了時に整数を返すことを表します。
int main(void)
↑
終了時に整数を返す
この2つは役割が違います。
| 見る場所 | 役割 |
|---|---|
main の前にある int
|
関数が返す値の型 |
main( ) の中にある void
|
関数が受け取る値がないこと |
3. return 0; は何を返しているのか
main 関数の最後には、次のコードを書いています。
return 0;
これは、プログラムを起動した側へ、終了結果を返す処理です。
main 関数
↓
return 0;
↓
プログラムを起動した側へ 0 を返す
↓
正常に終わったことを伝える
一般的には、次のように扱います。
| 戻り値 | 意味 |
|---|---|
0 |
正常に終了した |
0以外 |
途中で問題があり、正常には終了していない |
たとえば、必要な入力がなかった場合は、return 1; として終了できます。
#include <stdio.h>
int main(void)
{
printf("エラーが発生したため、処理を終了します。\n");
return 1;
}
WSLで終了結果を確認する
WSLのターミナルでは、プログラムを実行した直後に次のコマンドを実行すると、直前のプログラムが返した値を確認できます。
./return_example
echo $?
return 0; のプログラムなら、次のように表示されます。
0
return 1; のプログラムなら、次のように表示されます。
1
最初のうちは、すべてのプログラムで終了結果を確認する必要はありません。
ただし、あとでシェルスクリプトや自動実行を扱うときは、終了結果が「次の処理を続けるか」を判断する材料になります。
return 0; は省略できるのか
現在のC言語では、main 関数の最後まで到達した場合、return 0; を書いた場合と同じように正常終了として扱われます。
そのため、次のコードも動作します。
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n");
}
ただし、このシリーズでは return 0; を書くことにします。
理由は、次の2つです。
プログラムがどこで終わるかを初学者が確認しやすい
↓
return 1; などの異常終了と並べて理解しやすい
4. int main() ではなく int main(void) と書く理由
次の2つは、見た目がよく似ています。
int main()
int main(void)
どちらも、main 関数の定義としては引数なしで書かれているように見え、GCCでも受け付けられます。
ただし、C言語では () を使う書き方は、引数の型を明示しない古い形式です。
特に、関数の宣言で次のように書いた場合は、「引数がない」ことを明確には表しません。
int process();
そのため、引数を受け取らないことを明確にしたい場合は、void を書きます。
int main(void)
| 書き方 | 学習用コードでの扱い |
|---|---|
int main(void) |
引数を受け取らないことが明確。これを使う |
int main() |
古い形式。新しく書くコードでは使わない |
この書き方なら、コードを読む人にも、コンパイラにも「この関数は引数を受け取らない」と明確に伝わります。
5. void main(void) は何が違うのか
次のようなコードを見かけることがあります。
void main(void)
{
/* 処理 */
}
void は「値を返さない」ことを表します。
そのため、void main(void) は、終了時にプログラムを起動した側へ終了結果を返せません。
int main(void)
↓
終了結果を整数で返せる
void main(void)
↓
終了結果を返す場所がない
WSL + GCCでは警告になる
この記事で使っているコンパイル方法で、次のコードを確認します。
#include <stdio.h>
void main(void)
{
printf("Hello, World!\n");
}
gcc -std=c17 -Wall -Wextra -Wpedantic void_main.c -o void_main
GCCでは、次のような警告が出ます。
warning: return type of 'main' is not 'int'
警告が出ても実行できる場合はあります。
ただし、コンパイルできたことは、標準的で移植しやすい書き方であることを意味しません。
このシリーズでは、警告を残さないコードを書くため、void main は使いません。
では、void main は絶対に見ないのか
いいえ。古いコードや、特定の組み込み開発環境の資料などで見かけることはあります。
組み込み開発では、OSがある通常のアプリケーションとは異なり、プログラムの開始方法や終了の考え方が、処理系やプロジェクトのルールで決められている場合があります。
そのようなプロジェクトでは、顧客・製品・コンパイラのルールを優先します。
ただし、新しくC言語を書くとき、特にWSL + GCCで学習するときは、次の形に統一してください。
int main(void)
または、コマンドライン引数を受け取る場合は次です。
int main(int argc, char *argv[])
6. コマンドライン引数とは
ターミナルでは、実行ファイル名の後ろに文字を付けて実行できます。
./greet Haruto
この Haruto のように、プログラムを起動するときに渡す文字列を、コマンドライン引数と呼びます。
たとえば、次のような使い方があります。
指定した名前へあいさつする
↓
指定したファイルを読み込む
↓
指定した件数のデータを処理する
↓
指定した設定でプログラムを動かす
コマンドライン引数を受け取る場合、main 関数を次の形で書きます。
int main(int argc, char *argv[])
最初は長く見えますが、役割を分けると理解しやすくなります。
| 要素 | 役割 |
|---|---|
argc |
引数の数 |
argv |
引数として渡された文字列の一覧 |
7. argc は引数の数
argc は、argument count の略です。
プログラムを起動するときに渡された引数の数が入ります。
int main(int argc, char *argv[])
たとえば、次のように実行します。
./argument_count apple banana
この場合、通常は引数が3つとして扱われます。
argv[0] : ./argument_count
argv[1] : apple
argv[2] : banana
そのため、argc は 3 です。
実行ファイル名も、通常は先頭の要素として含まれます。
実行ファイル名
+ 利用者が指定した文字列
= argc に入る個数
引数の数を表示する
argument_count.c というファイルを作成してください。
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("引数の数: %d\n", argc);
return 0;
}
コンパイルします。
gcc -std=c17 -Wall -Wextra -Wpedantic argument_count.c -o argument_count
引数を付けずに実行します。
./argument_count
実行結果です。
引数の数: 1
./argument_count 自体が、先頭の要素として数えられているためです。
次に、2つの文字列を付けて実行します。
./argument_count apple banana
実行結果です。
引数の数: 3
8. argv は引数の文字列一覧
argv は、argument vector の略です。
「引数を並べたもの」と考えれば十分です。
char *argv[]
これは、文字列へのポインタを複数まとめたものです。
今は、次のように覚えてください。
argv[0] : 1つ目の文字列
argv[1] : 2つ目の文字列
argv[2] : 3つ目の文字列
argv に出てくる char * の詳しい意味は、ポインタと文字列の理解が進むと分かりやすくなります。
まずは、argv[index] で「起動時に渡された文字列を1つ取り出せる」と考えれば問題ありません。
すべての引数を表示する
show_arguments.c というファイルを作成してください。
#include <stdio.h>
int main(int argc, char *argv[])
{
// 先頭から最後まで、引数を1つずつ表示します。
for (int index = 0; index < argc; index++)
{
printf("argv[%d]: %s\n", index, argv[index]);
}
return 0;
}
コンパイルします。
gcc -std=c17 -Wall -Wextra -Wpedantic show_arguments.c -o show_arguments
次のように実行します。
./show_arguments apple banana
実行結果です。
argv[0]: ./show_arguments
argv[1]: apple
argv[2]: banana
実行環境によって、argv[0] の表示は ./show_arguments ではなく、別のパスやファイル名だけになる場合があります。
ただし、argv[1] 以降には、利用者が指定した文字列が順番に入ります。
9. 引数が足りない場合を確認する
たとえば、「名前を1つ指定して実行するプログラム」を作るとします。
正しい実行方法は、次のとおりです。
./greet Haruto
このとき、必要な引数の数は2つです。
argv[0] : 実行ファイル名
argv[1] : 名前
そのため、argc が 2 かどうかを確認します。
greet.c というファイルを作成してください。
#include <stdio.h>
int main(int argc, char *argv[])
{
// 実行ファイル名と名前の、合計2つが必要です。
if (argc != 2)
{
printf("使い方: %s <名前>\n", argv[0]);
return 1;
}
// argv[1] は、利用者が指定した名前です。
printf("こんにちは、%sさん。\n", argv[1]);
return 0;
}
コンパイルします。
gcc -std=c17 -Wall -Wextra -Wpedantic greet.c -o greet
名前を指定して実行します。
./greet Haruto
実行結果です。
こんにちは、Harutoさん。
引数を付けずに実行した場合は、次のようになります。
./greet
実行結果です。
使い方: ./greet <名前>
このように、最初に引数の数を確認すると、argv[1] が存在しない状態で使ってしまうミスを防げます。
10. コマンドライン引数は、すべて文字列
コマンドライン引数は、数字に見えても最初は文字列です。
たとえば、次のように実行した場合を考えます。
./repeat_message 3
argv[1] に入っているのは、整数の 3 ではありません。
文字列の "3" です。
argv[1]
↓
"3"
そのため、計算に使うには、文字列を整数へ変換する必要があります。
atoi ではなく strtol を使う
文字列を整数へ変換する関数として、atoi を見かけることがあります。
ただし、atoi は変換できなかった場合と、結果が 0 の場合を区別しにくいため、入力の確認が必要なプログラムには向きません。
このシリーズでは、変換できたかを確認できる strtol を使います。
名前や引数の細かい意味を、この時点で暗記する必要はありません。
まずは、次の3点を覚えれば十分です。
strtol は文字列を整数へ変換する
↓
変換できたかを確認できる
↓
変換に失敗した場合は、処理を止められる
指定した回数だけメッセージを表示する
repeat_message.c というファイルを作成してください。
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// 実行ファイル名と回数の、合計2つが必要です。
if (argc != 2)
{
printf("使い方: %s <回数>\n", argv[0]);
return 1;
}
// 変換できなかった位置を受け取るためのポインタです。
char *end_pointer = NULL;
// strtol が数値の範囲外を検出したかを確認するため、最初に 0 にします。
errno = 0;
// argv[1] の文字列を、10進数の整数として変換します。
long repeat_count = strtol(argv[1], &end_pointer, 10);
// 次のどれかに当てはまる場合は、正しい回数として扱いません。
// - 数値の範囲外だった
// - 数字を1文字も読み取れなかった
// - 数字の後ろに、数字以外の文字が残っている
// - int 型へ入れられる範囲を超えている
// - 0未満である
if (errno == ERANGE ||
end_pointer == argv[1] ||
*end_pointer != '\0' ||
repeat_count < 0 ||
repeat_count > INT_MAX)
{
printf("0以上の整数を指定してください。\n");
return 1;
}
// long から int へ変換しても問題ないことを確認した後で変換します。
int count = (int)repeat_count;
// 指定された回数だけ、メッセージを表示します。
for (int index = 0; index < count; index++)
{
printf("%d回目: C言語を学んでいます。\n", index + 1);
}
return 0;
}
コンパイルします。
gcc -std=c17 -Wall -Wextra -Wpedantic repeat_message.c -o repeat_message
次のように実行します。
./repeat_message 3
実行結果です。
1回目: C言語を学んでいます。
2回目: C言語を学んでいます。
3回目: C言語を学んでいます。
数値ではない文字列を渡した場合は、次のようになります。
./repeat_message three
実行結果です。
0以上の整数を指定してください。
strtol の細部は、今は覚えなくてよい
先ほどのコードには、これまでより多くの要素が出てきます。
errno
char *end_pointer
strtol(argv[1], &end_pointer, 10)
初めて見たときに、すべてを説明できる必要はありません。
ここで重要なのは、次の流れです。
ターミナルから "3" が渡される
↓
argv[1] に文字列として入る
↓
strtol で数値へ変換する
↓
変換できたかを確認する
↓
変換後の数値を for 文で使う
ポインタについては、先にこちらの記事を読むと char *end_pointer や &end_pointer を理解しやすくなります。
11. main 関数の書き方を選ぶ基準
最初は、次の基準で使い分けてください。
| やりたいこと | 書き方 |
|---|---|
| 起動時に外から値を受け取らない | int main(void) |
| 起動時にターミナルから文字列を受け取る | int main(int argc, char *argv[]) |
void main を使う |
新しく書くコードでは使わない |
学習用の小さなプログラムでは、ほとんどの場合、次の形で十分です。
int main(void)
{
/* 処理 */
return 0;
}
ファイル名や設定値、処理件数などをターミナルから指定できるようにしたい場合だけ、次の形へ変えます。
int main(int argc, char *argv[])
{
/* 処理 */
return 0;
}
12. 動かしながら試す課題
課題1:引数をすべて表示する
argv を使い、指定された引数をすべて表示してください。
実行例です。
./show_arguments red green blue
期待する表示例です。
argv[0]: ./show_arguments
argv[1]: red
argv[2]: green
argv[3]: blue
課題2:名前を2つ受け取る
次のように、名前を2つ指定して実行するプログラムを作ってください。
./greet_two_names Haruto Hana
期待する表示例です。
こんにちは、HarutoさんとHanaさん。
名前が1つだけ、または3つ以上指定された場合は、使い方を表示してください。
使い方: ./greet_two_names <名前1> <名前2>
課題3:指定した数までの合計を求める
次のように実行したとき、1から指定した数までの合計を表示してください。
./sum_to 10
期待する表示です。
1から10までの合計は55です。
数値以外が指定された場合は、エラーとして終了してください。
まとめ
main 関数は、プログラムの入口です。
最初は、次の書き方を使えば十分です。
int main(void)
この書き方は、次の意味です。
起動時に値を受け取らない
↓
main 関数から処理を始める
↓
return 0; で正常終了を返す
ターミナルから文字列を受け取りたい場合は、次の形を使います。
int main(int argc, char *argv[])
argc
= 引数の数
argv
= 引数の文字列一覧
void main(void) は、一部の環境で見かける場合があります。
ただし、WSL + GCC で標準的なCとして学ぶ場合、新しく書くコードでは使いません。
int main(void)
または、コマンドライン引数が必要な場合は次です。
int main(int argc, char *argv[])
に統一してください。
次は、商品・社員・センサー情報のように、複数の項目を1件のデータとして扱うために、構造体、typedef、enum、構造体の配列を学ぶと、扱えるプログラムの幅が広がります。