Another introduction to programming language C 02: 0から99まで印字するプログラムの解説

C言語で1から100まで印字するプログラムを書いてみる.ほとんどのCウィザード(C言語の達人)は次のようなコードを書く.

#include <stdio.h>
int main(int argc, const char *argv[]) {
  int i;
  for (i = 1; i <= 100; i++) {
    printf("%d\n", i);
  }
  return 0;
}

まず,i++をやめなさいと忠告する.i++は「iを評価した後iに1を加える」という特殊な演算子であることと,後置演算子(変数名の後ろに演算子を置くこと)であるため演算子そのものを見落としやすいためである.良い代替案は i += 1 である.この書き方は「iに1を加える」ということを端的に表している.

ソースコードを書き直してみよう.

#include <stdio.h>
int main(int argc, const char *argv[]) {
  int i;
  for (i = 1; i <= 100; i += 1) {
    printf("%d\n", i);
  }
  return 0;
}

解説する.

#include <stdio.h>

この行はstdio.hファイルの中身でそっくり置き換えられる.その理由は後述.

int main(int argc, const char *argv[]) {

Cのプログラムには必ずmainという関数が1個だけ必要である.プログラムはmainから実行される.これをプログラムのエントリーポイントと言う.intというのは,main関数が整数値(integer)を返すという意味である.(特段整数を返す理由も現在ではあまりないが,ほぼ歴史的な理由で整数を返すことになっている.黙って0を返すのが大人のプログラムである.)

int main(...)の...の部分,すなわち int argc, const char *argv[] のほうは,プログラム起動時に指定されたパラメタを受け取るためのメカニズムである.はっきり言って,使いづらいし,古くさい方法だ.このレクチャーでは,いずれ使い方を説明する予定ではあるが,今は無視しておいてもらいたい.読み方だけ説明すると「main関数は整数argcと,文字列の配列argvを受け取り,整数を返す関数である」と読める.

int i;

これは「これからiという変数を使います.iは整数です.」という「定義」である.「定義」はいつもメモリを確保する.つまり,裏の意味は「iという変数のためのメモリ(その大きさは整数1個分,だいたい4バイト)を確保しなさい.」という意味である.はっきり言えば,この書き方も古くさい.インタプリタはメモリは自動的に確保するので,インタプリタ系言語にはこのような「定義」の必要はない.

for (i = 0; i <= 100; i += 1) { ... }

forはループしなさいという意味である.モダンな言語では数学で言う「集合Aのすべての要素aについて...を実行せよ」をそのままプログラムで "for a in A: ..." のような書き方が出来るのであるが,Cはもちろん古くさい言語であるので,細かな指定が必要になる.Cのfor構文は

for (A; B; C) { ... }

の形をしているが,その意味は

A;
while (B) {
  ...
  C;
}

と同じ意味である.ここで while (B) { ... } はBが「真」である間...を実行せよという構文である.C言語では「真」の定義もいい加減なものであるが,大小関係や同値関係の判定ではおおむね字面を信じてもよい.例えば a > b と書いてあれば数学的にも同じ意味であるし,同値比較は a == b とイコールを2個書くことを除けば数学的に同じ意味である.(イコール1個で a = b とするとaにbの値を代入するという意味になってしまう.代入は数学では a := b または a ≡ b または a ← b と書く.)

printf("%d\n", i);

これはiの値を印字せよという意味である.printfは関数であるが,C言語の他の関数に比べれば(引数の数が決まっていないという意味で)異端の関数である.使い方はイディオムだと思って覚えるしかない.もちろん,printfの使い方はプロでも忘れるし,教科書を参考にするのは恥ずかしいことではない.

さて,C言語には,(main関数を除いて)すべての関数は事前に「宣言」または「定義」されていないといけないというルールがある.定義とはまさにmain関数がそうであるとおり,関数の中身を記述することである.(また変数と同様,定義するということはメモリが確保されると言うことでもある.)

関数の宣言(C言語ではもったいをつけてプロトタイプ宣言と言う)は,関数定義の1行目だけを書くことである.main関数をもし宣言するとすれば次のようになる.

int main(int argc, const char *argv[]);

この宣言は,後続の関数に「main関数とは,引数に整数1個と文字列の配列をとり,整数を返す」という意味を伝える.典型的には,数学関数

double sin(double x);

の宣言が分かりやすい.関数sinは実数(IEEE倍精度浮動小数点形式略してdouble型)を1個とり,実数を返す.

printf関数に話を戻す.printf関数はここに初めて出てきたわけであるから,そのままではエラーである.printf関数の宣言がmain関数の前に必要である.(main関数の冒頭というオプションもあるが,まず使われることはない.)printf関数の正しい宣言は以下のようなものである.

int printf(const char *format, ...);

...は省略ではなく,ピリオド3個からなるキーワードである.これをmain関数定義の前に挿入しなければならないのであるが,いちいち覚えるのも書くのも面倒である.(だいたいprintf関数が整数を返すことを覚えているプログラマなどいるだろうか.)

printf関数を含めて,標準的な印字,キーボード入力関係の関数の宣言はstdio.hというファイルにすでに書かれている.これが #include <stdio.h> の本当の意味である.stdio.hをファイル冒頭に挿入しておけば(プログラマは「インクルードする」と言う),printf関数の宣言は自動的になされるのである.

return 0;

習慣的にmain関数は0を返して終了する.これはstdlib.hで宣言されている関数exitを使って

exit(0);

とすることと同じであるが,(void型以外の)関数はreturnしなければならないという規則から,returnが必要である.注意!return構文は括弧付きでも書ける.

return(0);

は全く問題無くコンパイルされる.ただし,スペルミスをした場合,例えば

retrun(0);

はretrunという関数の呼び出しと間違えられる.(そのような関数が無ければコンパイルエラーになる.)この手のエラーは原因が分かりにくいため,プロの間では「returnには括弧をつけるな」という格言がある.
Comments