ターミナルから実行するあらゆるプログラムには引数を渡せます。gcc -Wall hello.c -o hello は 4 つ渡しています。あなた自身の C プログラムは、main の 2 つの引数を通じてそれを読みます。仕組みはそれだけで、ライブラリ呼び出しも準備も要りません。
シグネチャ
ここまでのドキュメントの例はすべて int main(void) を使ってきました。もう 1 つの標準的な形はコマンドラインを受け取ります:
int main(int argc, char *argv[]) {
/* ... */
}
argc("argument count")は引数がいくつあるかで、プログラム名を含みます。argv("argument vector")は文字列の配列です。char *argv[]は「char へのポインタの配列」、つまり C言語の文字列の配列を意味します。
char **argv という書き方も同じくらいよく見かけますが、こちらも等価です。配列の仮引数はポインタへ減衰するので、2 つは同じものを宣言しています。
エディタの Args パネルで引数を与えてから Run を押してください(1つのフィールドが1つの引数です)。hello、world、42 の3つのフィールドを試してみましょう。
構造
./greet Ada Lovelace を実行するとこうなります:
| インデックス | 値 | |
|---|---|---|
argv[0] | "./greet" | 呼び出されたときのプログラム名 |
argv[1] | "Ada" | 最初の本当の引数 |
argv[2] | "Lovelace" | 2 つ目の本当の引数 |
argv[3] | NULL | 終端 |
argc | 3 | argv[0] を含む個数 |
覚えておく価値のある事実が 4 つあります。
argcは実際上つねに 1 以上です。argv[0]がプログラム名だからです。本当の引数はインデックス 1 から始まり、だからそれをたどるループはi = 1から始まります。- すべては文字列です。
./prog 42が渡すのは"42"という 2 文字であって、数値の 42 ではありません。変換するのはあなたの仕事です。 argv[argc]はNULLです。規格が保証しているので、好みに応じてargcなしで配列を歩くこともできます:for (char **p = argv + 1; *p != NULL; p++)。- 語を分割するのはシェルであって、あなたのプログラムではありません。
./prog "Ada Lovelace"は1 つの引数です。引用符はシェルが消費するので、argvには届きません。
読む前に確認する
渡されなかった引数は空文字列ではなく、あなたが触れる権利のないメモリです。まず必ず argc を検証しましょう:
この小さなプログラムには慣習が 2 つ入っており、どちらも Unix のツール全般で標準です。
- 使い方のメッセージは
stdoutではなくstderrへ出します。どこかへパイプされている出力を汚さないためです。 mainからのゼロ以外の戻り値は、プログラムを実行した相手に失敗を伝えます。0は成功、それ以外は何かがうまくいかなかったことを意味します。シェルスクリプトはこれを確認します。
ハードコードした名前ではなく argv[0] を表示すれば、名前を変えられていても別のパスから呼ばれていても、メッセージはユーザーの呼び出し方に合います。
数値の引数を変換する
argv[1] はテキストです。算術をするには変換しなければならず、使うべき関数は <stdlib.h> の strtol です:
Args パネルに 10、20、30 を3つのフィールドとして追加し、それからそのうち1つを banana に変えてエラー経路を見てみましょう。
strtol の 3 つ目の引数は基数です。10 進なら 10、16 進なら 16、0x や先頭のゼロから自動判別させるなら 0 です。2 つ目は、消費しなかった最初の文字へのポインタを格納する場所で、これこそがエラーチェックを可能にします。
end == textは何も解析できなかったこと、つまり引数が数字で始まっていなかったことを意味します。*end != '\0'は数値のあとに残りのテキストがあることを意味するので、"12abc"は黙って 12 になるのではなく拒否されます。errno == ERANGEは値がlongからあふれたことを意味します。
短く書けるけれど、これらを何ひとつ報告できない atoi と比べてみましょう:
int n = atoi(argv[1]); /* "banana" -> 0, "0" -> 0, overflow -> undefined */
atoi は不正な入力に対して 0 を返しますが、それは本物のゼロと見分けがつきません。入力がプログラムの外から来るときは常に strtol(浮動小数点なら strtod)を使いましょう。詳しくは文字列の変換で扱います。
小さな電卓
まとめてみましょう。数値、演算子、数値を受け取るプログラムです:
12 + 30、次に 7 x 6、それから 5 / 0 を試してみましょう。
op == "+" ではなく strcmp(op, "+") == 0 である点に注目してください。char * の値を == で比べるのは中身ではなくアドレスの比較であり、まったく同じテキストでも偽になります。文字列関数を参照してください。
* ではなく x を使っているのは現実的な事情です。ほとんどのシェルは、あなたのプログラムが動き出す前に裸の * をカレントディレクトリのファイル一覧へ展開してしまいます。引用符で囲む('*')のでも動きますが、シェルが無視する文字を選ぶほうが親切です。
フラグを解析する
オプションは慣習的に - で始まります。単純な手書きのループで、よくある場面は扱えます:
-v、-n、3、hello の4つの Args フィールドで試してみましょう。
argv[++i] の前にある i + 1 >= argc のチェックが重要な行です。値を必要とするオプションは、その値が実際にあることを確かめなければなりません。さもないと -n が最後の引数だったとき、配列の末尾を越えて読んでしまいます。実際の Unix システムでは <unistd.h> の getopt がこれらをすべて引き受けてくれ、-vn3 のようにまとめた短いオプションにも対応します。フラグが 2 つ 3 つを超えたら、手を伸ばす価値があります。
ブラウザのエディタでの引数
このページのエディタブロックはこちらでコンパイルして実行しており、Args パネルが、ターミナルならプログラム名のあとに続けて書く内容を入力する場所です。Add arg で追加したフィールドは、1つがちょうど argv の1要素になります。あいだにシェルは入らないので、スペースで分割されることはなく、引用符も要りません。hello world を含むフィールドは、hello world という1つの引数として届きます。-n 3 hello を渡したいなら、3つのフィールドを追加してください。パネルを空のままにすると argc == 1 になります。最初の実行で argc < 2 のガードが働くのはそのためであり、それを書く価値があるのもそのためです。
よくある間違い
argcを確認せずにargv[1]を読む。 引数が渡されていなければ未定義動作であり、この分野で最も多いクラッシュです。- ループを
i = 0から始める。 プログラム名をデータであるかのように処理してしまいます。 argv[1]を数値として扱う。 変換するまではテキストです。- ユーザー入力に
atoiを使う。 不正な引数とゼロを区別できません。 - 文字列を
==で比べる。strcmpを使いましょう。 argvの文字列に書き込む。 規格上は書き換えが許されていますが、領域は限られていてプラットフォーム依存です。変更が必要なら自分のバッファへコピーしましょう。
よくある質問
C言語の argc と argv とは何ですか?
int main(int argc, char *argv[]) の 2 つの引数です。argc はプログラム名を含むコマンドライン引数の個数、argv はそれらの引数を文字列として並べた配列です。argv[0] はプログラム名、argv[1] が最初の本当の引数で、argv[argc] は常に NULL です。
C プログラムに引数を渡すには?
ターミナルで実行ファイル名のあとに書きます。./program hello 42 とすれば argc == 3 になり、argv[1] は文字列 "hello"、argv[2] は文字列 "42" です。このページのエディタでは、ターミナルの代わりに Args パネルに引数を入力します。
C言語でコマンドライン引数を int に変換するには?
strtol を使います。char *end; long n = strtol(argv[1], &end, 10); と書いたら、end がまだ先頭を指していないか(何も解析できなかった)と、*end が '\0' であるか(末尾にゴミがない)を確認します。atoi は短く書けますが、"0" でも "banana" でも 0 を返すので、エラーを報告できません。
C言語の argv[0] とは何ですか?
プログラムが呼び出されたときの名前で、たいていは ./program かフルパスです。使い方のメッセージ(fprintf(stderr, "usage: %s FILE\n", argv[0]))に使うと、ユーザーが実際に呼び出した形にメッセージを合わせられます。まれに空文字列になることもあるので、必ず存在すると決めつけないでください。