main()とは
main()は、C言語のプログラムで最初に呼び出される関数です。新たなプログラムを作成する場合は、まずはmain()を書き、そのmain()の中に実際に実行したいプログラムを書いていきます。
プログラムの実行時にmain()関数を呼び出しているのは、Cランタイムルーチンというプログラムです。C言語のプログラムをコンパイルすると、コンパイラ(正確には後述のリンカ)によってCランタイムルーチンと自動的にリンクされます。このため、コンパイラが作成した実行バイナリファイルを実行すると、その中に埋め込まれたCランタイムルーチンがmain()を呼び出すという仕組みになっているのです。
Cランタイムルーチンはアセンブラレベルでの初期化を行うために必要なもので、Linuxの場合は/usr/lib/crt1.oという、リロケータブルオブジェクトファイル[1]として存在します。
Cのシステムにとってのmain()
Cコンパイラ本体にとっては、main()はあくまで関数のひとつです。誤ってmain()のないプログラムを記述したり、mainのスペルを間違えたりしてもCコンパイラ本体にとっては正しいプログラムです。
main()のないプログラムをコンパイルしても、コンパイル自体は正常に行われます。そして最後に行なわれるCランタイムルーチンとのリンクの段階[2]で、「mainというシンボル名がない」というエラーになります。
なお、より規模の大きいプログラムでは、プログラムを複数のソースファイルに分けて分割コンパイルする場合があります。この場合は複数のソースファイルのうちの1つのみにmain()を記述します。誤って複数のソースファイルにmain()を記述してしまうと、先の例と同様にコンパイル自体は正常に行われますが、最後のリンクの段階で「mainが重複定義されている」というエラーが発生します。
printf()とは
hello.cでは、「Hello World」という文字列を画面に表示するのにprintf()を使いました。このprintf()も、main()と同様に関数の1つです。したがって、Cコンパイラ自身はprintf()という関数を知りません。
printf()のようにC言語で標準的に使用される関数は、libc(標準ライブラリ関数)としてまとめられており、プログラムのコンパイル時にはリンクの段階でlibcとのリンクが行なわれます。ちなみに、libcはLinuxでは/lib/libc.so.*に共有ライブラリとして存在します。
このように、C言語ではprintf()などの入出力に関する処理は、言語自体で何らかの文として定義されているのではなく、あくまでひとつの関数として、C言語本体からは切り離されているのです。これは、BASICのPRINT文やFORTRANのWRITE文が、関数ではなく言語自体で文として位置づけられているのとは異なる、C言語の特徴のひとつと言えます。
returnとは
returnは関数ではなく、C言語の予約語(後述)であり、C言語の制御文のひとつです。main()関数の最後には「return 0;」と記述して、OSにゼロという戻り値を返すようにしてください。第3回に出てきたリスト2.2のhello_simple.cの例のように、main()関数内の最後のreturnを省略しても、プログラム自体は一応問題なく動作しますが、これではOSに正しい戻り値を返したことにはなりません。
一般にOS上で動作するコマンドは、その終了時に戻り値(終了ステータス)をOSに返す必要があります。この戻り値は、コマンドが正常終了したかどうかを表しており、正常終了した場合はゼロ、何らかのエラーがあった場合はエラーの内容に応じたゼロ以外の値になります。この戻り値は、Makefile中で一連のmake作業を続行するか、エラーで中断するかの判断に利用されたり、シェルスクリプトで各種条件判断に利用されたりしています。なお、正常な場合にゼロになるというのは、C言語のif文などでの真偽(後述)とは逆になります。
hello.cのようなサンプルソースをコンパイルして作成しただけのコマンドであっても、このコマンドの実行、すなわち「Hello World」の表示が正常に行われ、問題なくmain()関数の実行を終了したのなら、戻り値としてゼロを返すべきです。
コマンドの戻り値は、実行例[3]ようにechoコマンドでシェル変数の「$?」の値を表示することによって確認できます。この例では戻り値として0が返ってきていることがわかります。なお、「echo $?」はa.outの実行の直後に実行する必要があります。