Cプログラミング入門

第5回 Chapter 2 Cプログラミング最初の一歩~Hello Worldからはじめよう(3)

この記事を読むのに必要な時間:およそ 2 分

main()とは

main()は,C言語のプログラムで最初に呼び出される関数です。新たなプログラムを作成する場合は,まずはmain()を書き,そのmain()の中に実際に実行したいプログラムを書いていきます。

プログラムの実行時にmain()関数を呼び出しているのは,Cランタイムルーチンというプログラムです。C言語のプログラムをコンパイルすると,コンパイラ(正確には後述のリンカ)によってCランタイムルーチンと自動的にリンクされます。このため,コンパイラが作成した実行バイナリファイルを実行すると,その中に埋め込まれたCランタイムルーチンがmain()を呼び出すという仕組みになっているのです。

Cランタイムルーチンはアセンブラレベルでの初期化を行うために必要なもので,Linuxの場合は/usr/lib/crt1.oという,リロケータブルオブジェクトファイル※1として存在します。

※1
ほかのプログラムと結合(リンク)して利用することができるコンパイル済みのファイル

Cのシステムにとってのmain()

Cコンパイラ本体にとっては,main()はあくまで関数のひとつです。誤ってmain()のないプログラムを記述したり,mainのスペルを間違えたりしてもCコンパイラ本体にとっては正しいプログラムです。

main()のないプログラムをコンパイルしても,コンパイル自体は正常に行われます。そして最後に行なわれるCランタイムルーチンとのリンクの段階※2で,「mainというシンボル名がない」というエラーになります。

※2
リンクの段階などのコンパイルの段階については次章で解説します。

リスト2.4 hello_func.c(main()をfunc()に変更)

#include <stdio.h>

int
func()
{
  printf("Hello World\n");
  return 0;
}

図2.7 hello_func.cをコンパイルし,mainがない場合のエラーメッセージ

$ gcc hello_func.c
/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crt1.o: In function `_start':
/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crt1.o(.text+0x18): undefined reference to `main'
collect2: ld returned 1 exit status
$

なお,より規模の大きいプログラムでは,プログラムを複数のソースファイルに分けて分割コンパイルする場合があります。この場合は複数のソースファイルのうちの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の実行の直後に実行する必要があります。

図2.8 コマンドの戻り値の確認

$ ./a.out
Hello World
$ echo $?
0
$
※3
「$?」が使えるのはbashなどのBシェル系の場合で,Cシェル系ではやり方が異なります。

returnとexit()

実際には,main()関数内で「retrun 戻り値;」という文を実行すると,プログラムの制御がいったんCランタイムルーチンのcrt1.oに戻ったあと,このcrt1.oから「exit(戻り値);」という関数が呼び出されます。exit()関数は,プログラムを終了してOSに戻り値を返すシステムコールを実行します。

なお,return文を使わずにexit()関数を直接呼び出すことも可能です。とくに,main()関数以外の関数内でプログラムを終了する場合は,プログラムの流れがmain()には戻ってこないため,exit()を直接呼び出す必要があります。

著者プロフィール

山森丈範(やまもりたけのり)

「C言語はアセンブラを触っているうちに自然に覚えてしまった」というプログラマ。C言語とのかかわり上,OSは専らUNIXを使用。C言語のプログラムでは,いつもLinux/FreeBSD/Solarisすべてで動くことを気に留めている。C言語と同様に移植性の高いシェルスクリプトにも思い入れが深く,著書に『シェルスクリプト基本リファレンス』がある。

著書

コメント

コメントの記入