[[PageOutline]] = C 言語チュートリアル = == C 言語って何? == UNIX システム発祥の、割と歴史が古いプログラミング言語です。古い言語でありながら、今でも OS や各種アプリケーションの開発に広く使われています。 C 言語の雰囲気になれて頂くために、まずは簡単な例として、1000 以下の素数をすべて求めて表示するプログラムを以下に示します。 {{{ #include #include #include #define MAX_CALC_NUM 1000 #define INTERNAL_BUFFER_SIZE (BUFSIZ * 2) #define PRIME_BUFFER_UNIT 500 int genNextPrime(); void printByComma(int); int main() { int n; do { n = genNextPrime(); printByComma(n); } while (n != 0); return 0; } int genNextPrime() { static int n = 2, length = 0, *buf = NULL; static size_t buf_size = 0; int i; if (!buf) { buf = calloc(PRIME_BUFFER_UNIT, sizeof(int)); buf_size = PRIME_BUFFER_UNIT; } for (; n <= MAX_CALC_NUM; n++) { for (i = 0; i < length; i++) { if (buf[i] * buf[i] > n) { i = length; break; } if (n % buf[i] == 0) break; } if (i == length) { /* 素数を発見 */ buf[length++] = n; if (length == buf_size) { buf_size += PRIME_BUFFER_UNIT; buf = realloc(buf, buf_size * sizeof(int)); } return n++; } } /* 素数算出対象の最大値に達した */ free(buf); n = 2, length = 0, buf = NULL, buf_size = 0; return 0; } void printByComma(int n) { static char line_buffer[INTERNAL_BUFFER_SIZE]; static int is_first = 1; static size_t line_len; char buffer[BUFSIZ]; if (is_first) { memset(line_buffer, 0, INTERNAL_BUFFER_SIZE * sizeof(char)); line_len = 0; } if (n == 0) { puts(line_buffer); is_first = 1; return; } sprintf(buffer, is_first ? "%d" : ", %d", n); line_len += strlen(buffer); strncat(line_buffer, buffer, BUFSIZ); is_first = 0; if (line_len < BUFSIZ) return; strncpy(buffer, line_buffer, BUFSIZ - 1); buffer[BUFSIZ - 1] = '\0'; printf("%s", buffer); strcpy(line_buffer, line_buffer + BUFSIZ - 1); line_len -= BUFSIZ - 1; } }}} === 「関数による」手続き型言語 === C 言語は、すべての'''手続き''' (これは「命令」と置き換えて読んで頂いても良いでしょう) を'''関数'''として書き表す、というスタイルを採用したプログラミング言語です。関数呼び出しが階層的・構造的であるため、'''構造化言語'''などと呼ばれたりもします。 上記のサンプルの場合、手続きは以下のように構造化されます。 * メイン関数 ('''main''') * 繰り返し (do ~ while) - 素数を求め終わるまで繰り返す * 素数を求める ('''genNextPrime''') * 素数バッファのメモリー領域を確保する ('''calloc''') * 繰り返し (for) - 判定対象となる値をカウントする * 繰り返し (for) - 過去に求めた素数を小さい順にたぐる * 評価対象の素数の二乗が判定対象となる値より大きい (buf[i] * buf[i] > n) ならば、それ以上に大きい素数で割りきれることはあり得ない。 - 判定対象の値は素数であるものとして、繰り返しを抜ける。 * 評価対象の素数で判定対象の値が割りきれる (n % buf[i] == 0) ならば、判定対象の値は素数ではないものとして、繰り返しを抜ける * 判定対象の値が素数ならば、 * 素数バッファにこの値を追加し (buf[length++] = n)、 * 素数バッファがいっぱいになったならサイズを拡張し ('''realloc''')、 * この値を返す (return n++)。 * 判定対象の値が 1000 を超えたら、素数バッファのメモリー領域を解放し ('''free''')、すべての素数を求め終わったことを示す値 0 を返す (return 0) * 求めた素数を表示する ('''printByComma''') * 初めての呼び出しならば (is_first)、書き溜め用のバッファ領域を初期化する ('''memset''')。 * 渡された値が素数ではなく (すべての素数を求め終わったことを示す値) 0 ならば、書き溜め用のバッファ領域に残っている文字列をすべて表示し ('''puts''')、関数を抜ける (return)。 * 素数の値を文字列に変換する ('''sprintf''')。 * 上で変換された文字列の文字数を求め ('''strlen''')、書き出し用バッファに既に書き出されている文字数との合計を算出する。 * 書き出し用バッファ上の文字列に、上で変換された文字列を繋げる ('''strncat''')。 * 書き出し用バッファ上の文字数が一定数を超えた場合、その文字数までを一旦別のバッファにコピーしてから ('''strncpy''') 表示し ('''printf''')、表示した分の文字列を領域から取り除く ('''strcpy''')。 この中で、括弧書きで強調表示しているのはすべて関数名です。 main 関数と genNextPrime 関数、 printByComma 関数はサンプルのプログラム中で定義している関数ですので、その関数の中での手続きも合わせて書き出してみました。こうしてみると、処理内容をツリー状に書き表せるということがよく分かると思います。 C 言語はこのように、 main 関数から処理が開始され、関数の中で別の関数を呼び出し、その関数がまた更に別の関数を呼び出す、ということの繰り返しによって、プログラムを成り立たせる。そんなスタイルの言語なのです。 === 言語として最低限の命令しか用意していない === C 言語が、言語として用意している命令は、ごくわずかです。どんなものがあるかというと、概ね以下の通りです。 * 宣言子 (int, char などの型指定子、 const, volatile などの型修飾子、 その他、 struct, enum, typedef 等々…)。 * 処理の流れ制御 (if, switch などの分岐、 for, while などのループ、 ループから抜ける break やループを強制的に回す continue 、関数を抜ける return 等々…)。 * 演算子 (四則演算の +, -, *, / 、代入 = 、ポインタ演算子 * やアドレス参照 & 、括弧 () 、比較 <, >, == 、等々…)。 それ以外のことは、すべて関数で表現します。例えば、画面に文字を表示するのも、 C 言語に用意された命令ではなく、'''誰かが'''用意した関数を呼び出すことで実現するのです。 …といっても、心配することはありません。例えば、上記サンプルプログラムにおいても、画面に文字を表示するのに、 printf や puts といった名前の関数を利用していますが、これらはプログラマーが自分で定義した関数ではなく、 C コンパイラーを作った人々 (ベンダー) が用意している'''標準ライブラリ'''において用意されている関数です。標準ライブラリにどのような関数が用意されているかは、 ANSI や ISO などによって標準規格として定められており、それらの関数を用いる範囲内においては、ある程度の可搬性 (即ち、 OS や C コンパイラが変わっても、コンパイルが通り、同じような動作をしてくれること) が期待できるのです。