第17回
コマンドライン引数と終了コード~main関数の基本事項

コマンドライン引数と終了コードの応用例

ここまでのことを踏まえて、yesno.cから生成された実行形式ファイル(MS-DOSではYESNO.EXE、UNIX系OSではyesno)の、コマンドライン引数と終了コード(戻り値)の扱いに関する応用例を見ておきましょう。

MS-DOSのバッチファイル

リスト2:コンパイル・エラー修正・再コンパイルを自動実行するMS-DOSのバッチファイル
ECHO OFF
CLS
IF [%1] == [] GOTO ERR
:START
    ECHO %1 コンパイル中...
    LCC -j -oC:\CLANG\EXE\%1.EXE C:\CLANG\SRC\%1.C > C:\CLANG\SRC\%1.ERR
    IF ERRORLEVEL 1 GOTO EDIT
    GOTO SUCCESS
:EDIT
    C:\WINDOWS\SYSTEM32\NOTEPAD.EXE C:\CLANG\SRC\%1.ERR

    YESNO 再コンパイルしますか? -------- (a)
    IF ERRORLEVEL 1 GOTO EXIT ---------- (b)
    GOTO START
:SUCCESS
    ECHO コンパイルは成功しました!
    GOTO EXIT
:ERR
    ECHO CMK : ファイル名を指定してください。拡張子は不要です。
:EXIT

リスト2は、yesno.cをコンパイルしたyesno.exeをMS-DOSのバッチファイルで利用した例です。これも、詳しい意味を説明しないままに第13回で紹介しました。

まず、バッチファイルの仕組みを簡単に紹介しておきましょう。

バッチファイルでは、コマンドライン引数を記述された順に%1、%2……という記号で表し、それをバッチファイル内の他のコマンドの引数にできます。3行目の
IF [%1] == [] GOTO ERR
では、コマンドライン引数が指定されていない([]内が空)場合には、ERRと記述されたラベルに移行してエラーメッセージを出し終了する――という処理です。

続いて5行目の
ECHO %1 コンパイル中...
では、ECHOコマンドでコマンドライン引数(コンパイル対象のソースファイル名)と「コンパイル中」の文字列を画面に表示し、6行目の
LCC -j -oC:\CLANG\EXE\%1.EXE C:\CLANG\SRC\%1.C > C:\CLANG\SRC\%1.ERR
では、LSI-CコンパイラドライバLCCのコマンドライン引数として、実行形式ファイルの生成とエラーメッセージを記録するテキストファイルの生成に利用しています。

コマンドライン引数

リスト2の(a)の箇所
YESNO 再コンパイルしますか?
で、yesno.cをコンパイルしてできあがったYESNO.EXEを実行しています(MS-DOSでは実行形式ファイルの拡張子"EXE"を省略できます)。コマンドライン引数には「再コンパイルしますか?」という文字列を指定しています。

YESNO 再コンパイルしますか?
YESNO.EXEはコマンドライン引数をメッセージとして画面表示し、それに続けて<y/n>という文字列を表示します。つまり上の1行は、画面に
再コンパイルしますか? <y/n>
と表示して1文字の入力を待つ――という動作になります。

終了コードの取得と利用

(b)の行でIFによって判定しているERRORLEVELが、その直前に実行したYESNO.EXE(YESNO 再コンパイルしますか?──の行)の実行結果である戻り値=終了コードを保持した変数です。

    IF ERRORLEVEL 1 GOTO EXIT
先に実行したYESNO.EXEでユーザーが'Y'または'y'を入力していれば、その終了コードは0となってERRORLEVELに保存されます。それ以外の文字が入力されていればERRORLEVELには1が保存されます。

Linuxのシェルスクリプト

リスト3:コンパイルを自動化するUNIX系OSのシェルスクリプト
#! /bin/sh
if [ "$1" = "" ]
then
  echo "usage:cmk <filename>"
  exit 1
fi
# Compile the source file
status=0
gcc $HOME/src/$1.c -o $HOME/src/$1 > $HOME/src/$1.err
status=$?
while [ $status != 0 ]
do
# Display prompt and wating for user's answer 'y' or 'n'
  yesno "Edit and re-compile, ok?" -------- (c)
  if [ $? = 0 ] --------------------------- (d)
  then
    emacs $HOME/src/$1.c $HOME/src/$1.err
    gcc $HOME/src/$1.c -o $HOME/src/$1  > $HOME/src/$1.err
    status=$?
  else
    status=0
  fi
done
echo "cmk : terminated."

リスト3は、同じくyesno.cをLinuxのGCCでコンパイルした実行形式ファイルyesnoを、Linuxのシェルスクリプト(シェルにはshを指定)で利用した例です。これも、詳しい意味を説明しないままに第13回で紹介しました。

シェルスクリプトも基本的な構造はバッチファイルと同じで、ソース中に記述されたコマンドを順に実行していきます。if命令で条件を調べて処理の流れを切り替えられる点も変わりません。ただ、使用するシェル(ソースの先頭行で#!記号に続けて指定)によって文法の変わる部分があるため、注意が必要になります。

shで扱うシェルスクリプトでは、コマンドライン引数が$1、$2……という記号になります。2~6行目の
if [ "$1" = "" ]
then
  echo "usage:cmk <filename>"
  exit 1
fi
という箇所で、$1が""(空文字=引数なし)かどうかを調べ、処理を分岐しています。ifによる処理ブロックはfiで閉じます。

コマンドライン引数

(c)の箇所が、実行形式ファイルyesnoを実行しているところです。UNIX系OSでは必ずしもコンソールに日本語が使えるとは限らないので、yesnoの引数には"Edit and re-compile, ok?"という英文の文字列を与えています。

yesno "Edit and re-compile, ok?"
後の動作は、先に紹介したMS-DOSの場合と同じです。

終了コードの取得と利用

yesnoが実行された直後、その終了コードが変数"$?"に保存されます。その値を調べて処理を切り替えているのが、(d)からの8行です。
if [ $? = 0 ]
then
  emacs $HOME/src/$1.c $HOME/src/$1.err
  gcc $HOME/src/$1.c -o $HOME/src/$1  > $HOME/src/$1.err
  status=$?
else
  status=0
fi

まとめ

今回は、以下の点について説明しました。

1.main関数もスタートアップ・ルーチンから呼び出されること。
2.main関数の型であるint型はその戻り値(終了コード)の型であること。
3.main関数がreturnで返した戻り値はスタートアップ・ルーチンを経て呼び出し元のプログラムに渡されること。
4.main関数の引数にはint型のargcとchar型のポインタ配列argvがあること。
5.argcは引数の数を、argvは引数文字列へのポインタの配列を示していること。

4と5、特にポインタの配列という部分は難しいので、文字列とポインタについて説明した後で再度触れることにします。今回は、Cのソースの出発点であるmain関数の特徴的な機能について、その概要を把握しておいてください。

あとがき

hiropの『ちょっと気になる専門用語』~《呼び出す》

プログラミングでは、離れた場所にある小さな処理を実行することを「呼び出す」と言います。Cの関数だけではなく、BASICのサブルーチンやVisual Basicのプロシージャ、その他WindowsのAPI(Application Programming Interface)関数などOSの提供する機能を使う場合も、同様に「呼び出す」と表現します。

英語では"call"なので、直訳すれば「呼ぶ」となります。日本語のニュアンスでは単純に「呼ぶ」とするよりうまい訳だと思います。

この動作では、呼び出された機能で処理が終わったら処理の流れが呼び出し元に戻ることが前提となります。呼び出すとは「離れた場所にある誰かをこちらに呼んでくる」ことですから、呼び出した『こちら』が主体となる訳です。先に行ったきり戻らないのでは、呼び出しではなく放り出しになってしまいます。

実際、今回の例に示したバッチファイルやアセンブリ言語などでラベルを使って処理を移動する場合は「呼び出す」という表現を用いません。呼び出す側が「主」で呼び出される関数などの小さな機能が「従」という関係です。

ところで、英語(米語)のcallには「呼ぶ」の他に「電話をかける」「電話口に出す」という意味があります。昔、アイルランドの青年と話していて「君に電話する」を"I'll call you"と言ったら通じなかった経験があります。"telephone..."と続けたら、"Oh, you will ring me"と返されました。

アイルランドでは「電話をかける」ことを"ring"と表現するのだそうです。日本語で言えば「リンリンする」ですから、おそらく俗語でしょう。それにしても、いまどき「リンリン」と鳴る電話など珍しいのに、言語習慣とは面白いものです。日本語で、電子レンジで暖めることを「チンする」と言うのと似たようなものなのでしょう。

そう言えば、マウスボタンの「クリック(click)」も擬音の「カチッ!」ですよね。爆弾の"bomb"も日本語で言えば「ドカン!」という擬音です。欧米言語には音で様子を表す擬音語がたくさんありますね。一方、日本語の「ぐんぐん」「ざわざわ」といった擬態語は存在しません。これらは動詞を修飾する副詞に取って代わられています。

日本語では、「呼ぶ(yobu)」から転じて「読む(yomu)」という言葉が生まれました。読むことは、書物に書かれた文章の真意を自分の側に引き寄せて解釈する行為なのです。だから将来を予測することを「先を読む」などと言うのでしょう。

とまぁ、話があっちこっちへ飛んでしまいました。ちなみに、我が家の電子レンジはIPv6対応でRJ45ジャックが設けられているのですが、一度も使ったことがありません。ハッカーに食生活を覗かれたら……と、危惧している訳じゃないんですけど(^^ゞ