C言語の入門書を読む:『情報処理技術者テキスト プログラミング入門 C言語』の補足

こんなこと一般常識*1だし、既に沢山の人が言及してると思うけど、補足しておく。以下、ANSI規格やANSI C規格と書いてあったら、特に断りの無い限りANSI C89を意味すると思って欲しい。

ANSI Cに基づくmain()の型

ANSI C規格では、プログラムを実行する環境として次の2種類を定めている。

ホスト環境
Cで書いたプログラムがOSの制御下で実行される環境。普通にパソコン上でプログラムを書いてパソコン上で実行している場合はホスト環境になる。ホスト環境ではプログラム実行時にmain()に制御が移るので、プログラムはmain()を1個用意しておかなければならない――という仕様になっている。
自立環境
CプログラムがOSに依存せずに実行される環境。例えば組込みソフトの開発ではOSを使わない場合がある。この環境では、main()については特に何も定められていない。プログラム開始時にどんな関数を呼んでも構わないので、main()が存在しない可能性もある。

main()について問題になるのはホスト環境での話。ホスト環境におけるmain()の正しい宣言については、これまた規格で次の通りに定められている。

  • int main(void)
  • int main(int argc, char **argv)

但し**argvは*argv[]としてもよいし、argcとargvには別の名前をつけてもよいことになっている。

以上、規格から見た場合、ホスト環境においては、ANSI C規格に準拠している限り「int main(void)」や「int main(int argc, char **argv)」などと書くのが正しい。

もちろんこれはANSI C規格に準拠する場合の話なので、規格に準拠しないとか処理系で独自拡張してそれ以外でもOKとすることが禁止されている訳ではない。*2

もうちょっと掘り下げてみる

ここからはANSI C規格から離れた話*3。少なくとも普通にホスト環境用のちょっとしたプログラムを書く分にはあまり縁がない話。

main()について定められているのはホスト環境、つまりOSの上でプログラムを動かす場合だ。これはどういうことなのか?

低水準の世界に詳しくない私は、こう理解している。

  1. OS上でプログラムが実行できる状態になるまでには色々と準備が必要。
  2. この準備はOSとCランタイムライブラリ(crt0*4 *5)あたりが担当。
  3. crt0は、プログラムをビルドする時にこっそりリンクされている。
  4. プログラム実行時には、crt0で色々と準備してから、crt0の中からmain()がコールされる。で、main()でreturnするとcrt0に戻ってくる*6

C言語風に表現すると、crt0の中には、

int result;

/* 色々と省略 */

result = main(argc, argv);

といった感じのコードが含まれている。このmain()は自分が書いたプログラムのmain()な訳だ。

「void main()」がマズイ理由

自分が使っているコンパイラが、main()についてANSI規格に準拠していて、且つ全く独自拡張されていないと仮定する。

crt0には先ほど挙げたようなコードが含まれているはず。即ちcrt0側はmain()の型として、

  • int main(void)

ないし

  • int main(int argc, char **argv)

となっているはずだと仮定している。

この時、自分が書いたプログラムの側で戻り値の型を勝手にvoidにしてmain()を定義してしまうと、crt0としては「『result = main(argc, argv);』ってなっとるけど、void型の戻り値なんて取得できへんやんけ。かなわんなあ」となってしまう。*7

蛇足

ホスト環境用の処理系にはcrt0などのCランタイムが含まれているが、処理系によってmain()の戻り値などの定義が異なると、プログラムを別のOSに持っていってビルドした時にCランタイムとのリンクがうまくいかなくなる可能性がある。

なのでANSI C規格でmain()の正しい宣言について規定しているのではないか、少なくとも規定している理由の1つではないか、と妄想している。

*1:じゃなかったらどうしよう……いやそんなことはないか。

*2:C99では処理系で独自拡張すること自体が規格的に認められているらしい。

*3:だと思うけど、よく分からない

*4:静的リンクの場合。hpのドキュメントを見る限り、動的リンクの場合は事情が異なるみたい。

*5:どんなOSでもcrt0という名前が使われているのかどうかは、調べてないので不明。

*6:exit()の場合は知らない。

*7:というかリンカに「『int main(int argc, char **argv)』な関数なんてねぇぞ!」と怒られる気がする。