コマンドライン引数は、プログラムの動作を実行時に切り替える最も基本的な方法です。
本記事では、C言語のmain
関数が受け取るargc
とargv
を中心に、書き方、使い方、実行方法、そして初心者がつまずきやすい注意点まで、ていねいに解説します。
C言語のコマンドライン引数とは
コマンドライン引数の役割
プログラムは、実行時に引数を受け取ることで挙動を柔軟に変えられます。
たとえば、入力ファイル名、動作モード、数値パラメータなどをコマンドラインから渡せば、同じプログラムでも用途に応じて使い分けることができます。
コンパイルし直さずに動作を変更できるのが大きな利点です。
argcとargvの意味
C言語ではmain
を次の形で宣言すると、実行時の引数を受け取れます。
argc
は引数の個数、argv
は引数の文字列配列です。
以下の表で概要を整理します。
項目 | 型 | 役割 | 例 |
---|---|---|---|
argc | int | 引数の個数 | 3なら、argv[0], argv[1], argv[2]が有効 |
argv | char*[] または char** | 引数文字列へのポインタ配列 | 各要素はヌル終端文字列 |
argv[argc] | char* | ヌルポインタ(nullptr) | 番兵。文字列ではないので参照しない |
配列の範囲は0
からargc-1
までです。
番兵としてargv[argc]
はヌルポインタですが、ここを「最後の文字列」として参照してはいけません。
argv[0]は実行ファイル名
argv[0]
は通常、実行ファイル名や起動に使ったパスを指します。
環境によってはフルパス、相対パス、あるいはシンボリックリンク名やラッパースクリプト名が入ることがあります。
必ずしも純粋なファイル名とは限らない点に注意します。
引数は文字列として渡る
コマンドライン引数はすべて文字列として渡されます。
数値として扱いたい場合はstrtol
やatoi
で変換します。
初心者のうちはエラー検出ができるstrtol
を推奨します。
atoi
は失敗時の判定ができず、安全性に欠けます。
mainの書き方と使い方
基本の宣言(int main(int argc, char* argv[]))
コマンドライン引数を受け取るmain
の代表的な宣言は次のとおりです。
int main(int argc, char* argv[])
int main(int argc, char** argv)
どちらも意味は同じです。
引数を受け取らない形としてint main(void)
も存在しますが、本記事のテーマでは前者を用います。
argvの参照方法
argv
は「文字列へのポインタ」の配列です。
argv[i]
がi
番目の引数の先頭アドレスを指し、そこにヌル終端文字列が置かれています。
通常、argv[0]
はプログラム名、argv[1]
以降がユーザが指定した引数です。
ループで走査すると全体を確認できます。
引数の個数チェック
アクセス前に必ず個数を確認します。
たとえば、2個の引数を必要とする場合はargc
が期待値以上かチェックし、不足なら使い方を表示して終了します。
これにより範囲外アクセスを防げます。
サンプル
以下は、受け取った引数をすべて表示する簡単なプログラムです。
// ファイル名: echoargs.c
// 目的: 受け取ったコマンドライン引数を一覧表示する
// コンパイル例(GCC/Clang): gcc -o echoargs echoargs.c
// コンパイル例(MSVC): cl /EHsc echoargs.c
#include <stdio.h>
int main(int argc, char* argv[]) {
printf("argc = %d\n", argc);
// iは0からargc-1まで。argv[argc]はヌルポインタ(番兵)なので参照しない
for (int i = 0; i < argc; ++i) {
// %dでインデックス、%sで文字列を表示
printf("argv[%d] = \"%s\"\n", i, argv[i]);
}
return 0; // 正常終了
}
$ ./echoargs apple 123 "hello world"
argc = 4
argv[0] = "./echoargs"
argv[1] = "apple"
argv[2] = "123"
argv[3] = "hello world"
次は、2つの整数を受け取り、その合計を表示する例です。
文字列から数値への変換にstrtol
を用いてエラーを検出します。
// ファイル名: add.c
// 目的: 2つの整数引数を受け取り、合計を表示する
// 使い方: ./add 12 30 (結果: 42)
// コンパイル例(GCC/Clang): gcc -o add add.c
// コンパイル例(MSVC): cl /EHsc add.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
long parse_long(const char* s, int* ok) {
// strtolで10進として解釈
errno = 0;
char* end = NULL;
long v = strtol(s, &end, 10);
// 1) 変換できなかった(先頭から数字がない)
// 2) オーバーフロー/アンダーフロー
// 3) 末尾に不正文字が残った
if (s == end) {
*ok = 0;
} else if (errno == ERANGE || v > LONG_MAX || v < LONG_MIN) {
*ok = 0;
} else if (*end != '// ファイル名: add.c
// 目的: 2つの整数引数を受け取り、合計を表示する
// 使い方: ./add 12 30 (結果: 42)
// コンパイル例(GCC/Clang): gcc -o add add.c
// コンパイル例(MSVC): cl /EHsc add.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
long parse_long(const char* s, int* ok) {
// strtolで10進として解釈
errno = 0;
char* end = NULL;
long v = strtol(s, &end, 10);
// 1) 変換できなかった(先頭から数字がない)
// 2) オーバーフロー/アンダーフロー
// 3) 末尾に不正文字が残った
if (s == end) {
*ok = 0;
} else if (errno == ERANGE || v > LONG_MAX || v < LONG_MIN) {
*ok = 0;
} else if (*end != '\0') {
*ok = 0;
} else {
*ok = 1;
}
return v;
}
int main(int argc, char* argv[]) {
if (argc != 3) {
// 期待する引数個数は2(プログラム名を含めるとargcは3)
fprintf(stderr, "使い方: %s <整数A> <整数B>\n", argv[0]);
return 1; // 異常終了
}
int ok1 = 0, ok2 = 0;
long a = parse_long(argv[1], &ok1);
long b = parse_long(argv[2], &ok2);
if (!ok1 || !ok2) {
fprintf(stderr, "エラー: 整数に変換できない引数があります。\n");
return 1;
}
long sum = a + b;
printf("%ld\n", sum);
return 0;
}
') {
*ok = 0;
} else {
*ok = 1;
}
return v;
}
int main(int argc, char* argv[]) {
if (argc != 3) {
// 期待する引数個数は2(プログラム名を含めるとargcは3)
fprintf(stderr, "使い方: %s <整数A> <整数B>\n", argv[0]);
return 1; // 異常終了
}
int ok1 = 0, ok2 = 0;
long a = parse_long(argv[1], &ok1);
long b = parse_long(argv[2], &ok2);
if (!ok1 || !ok2) {
fprintf(stderr, "エラー: 整数に変換できない引数があります。\n");
return 1;
}
long sum = a + b;
printf("%ld\n", sum);
return 0;
}
$ ./add 12 30
42
$ ./add 12 abc
エラー: 整数に変換できない引数があります。
char* argv[] と char** argv の違い
関数パラメータとしてはどちらも同等です。
配列パラメータchar* argv[]
は関数宣言上char** argv
に調整されます。
可読性の観点で、argv
が「文字列へのポインタの配列」であることを示したいならchar* argv[]
を、よりポインタ寄りに捉えたいならchar** argv
を使います。
次の表は違いをまとめたものです。
宣言 | 意味合い | 実際の呼び出し時の型 |
---|---|---|
char* argv[] | 文字列へのポインタの配列という意図を表しやすい | 関数内ではchar**に調整される |
char** argv | 二重ポインタであることを明示 | そのままchar** |
どちらを選んでも動作は同じで、コードスタイルの問題です。
実行方法と確認手順
コンパイル手順
代表的なコンパイラでのコンパイル例を示します。
gcc -o echoargs echoargs.c
gcc -o add add.c
gcc -o echoargs.exe echoargs.c
gcc -o add.exe add.c
cl /EHsc echoargs.c
cl /EHsc add.c
コンパイル後、シェルで実行します。
Unix系シェル(bashやzsh)では次のように実行します。
./echoargs apple 123 "hello world"
./add 12 30
WindowsのPowerShellやコマンドプロンプトでは次のように実行します。
.\echoargs.exe apple 123 "hello world"
.\add.exe 12 30
スペースを含む引数
空白で引数が区切られるかどうかは「シェルのルール」に従います。
文字列にスペースを含めたい場合は引用符で囲むか、エスケープします。
./echoargs "hello world" path\ with\ spaces
.\echoargs.exe "hello world" 'path with spaces'
.\echoargs.exe "hello world" path^ with^ spaces
どのシェルを使っているかで引用やエスケープの書き方が変わるため、実行環境を意識して入力してください。
IDEでの設定
IDEを使う場合、プロジェクト設定で「コマンドライン引数」を指定できます。
代表例を挙げます。
- Visual Studio: プロジェクトのプロパティ → 構成プロパティ → デバッグ → コマンド引数
- Xcode: SchemeのEdit → Arguments → Arguments Passed On Launch
- CLion: Run/Debug Configurations → Program arguments
- VS Code: tasks.jsonやlaunch.jsonの
args
に配列で指定
GUI上であらかじめ引数を登録しておけば、毎回同じ引数で素早くテストできます。
よくあるミスと注意点
argc未チェックのアクセス
もっとも多い不具合は、argc
を確認せずにargv[1]
などへアクセスすることです。
引数が不足していると未定義動作になります。
最初にargc
のチェックを行う習慣をつけましょう。
添字の取り違え
argv[argc]
はヌルポインタであり、実データの終端を示す番兵です。
ここを%s
で出力しようとするとクラッシュの原因になります。
必ずi < argc
という条件でループしましょう。
argvを勝手に書き換えない
環境によってはargv[i]
が指す文字列を変更できる場合もありますが、初心者は読み取り専用と考えるのが安全です。
特にargv[i]
が文字列リテラルや読み取り専用領域を指す可能性がある実装では、書き換えが未定義動作になります。
空白の扱いはシェルのルール
引数の分割や展開はシェルが行い、C言語側では「結果の文字列」を受け取るだけです。
ワイルドカードの展開やクォートの挙動はシェルによって異なり、プログラムからは制御できません。
動作が合わないときは、実行しているシェルとそのクォート規則を確認してください。
まとめ
本記事では、C言語のmain
が受け取るargc
とargv
について、役割、宣言、参照方法、引数チェック、実行方法、そして注意点を体系的に解説しました。
引数はすべて文字列で渡されるため、必要に応じてstrtol
などで安全に変換し、アクセス前にargc
を必ず確認するのが基本です。
また、スペースやクォートの扱いはシェルのルールである点も忘れないでください。
これらの基礎が身につけば、設定ファイルなしでも柔軟に動作を切り替えられる、小さくて使いやすいプログラムを書く力が身につきます。