一般的にC言語処理系では実際にソースプログラムをコンパイルするまえに プリプロセッサ というプログラムを利用して事前処理として いくつかの書き換えを行なう。
たとえば、コメント文などはコンパイルのときには必要ないので、
プリプロセッサが事前にはぎ取る。もうひとつ代表的なものが
#
記号で始まる行の内容を特別に解釈する働きで、たとえば
#include <stdio.h>
という行があったときに、/usr/include/stdio.h
ファイルの
内容をその行(#include
のある行)の場所に取り込むといった処理
が該当する。
【体験してみよう】
実際に自分の作ったCのソースプログラムをプリプロセッサに掛けた結果を見
てみよう。プリプロセッサのコマンド名は cpp
である。
以前作ったもの(たとえば aisatsu.c
)をcppに処理させ、
その結果を less コマンドで見てみよう。
less
コマンドの終了には q をタイプする。
次のページを見るのは SPC、戻るのは b。
#include
と書いていた行が、置き換えられていることに
注意しよう。
#
記号で始まる、プリプロセッサに司令を与える
単語のことを ディレクティブ という。
define
ディレクティブプログラムを作っていて、将来的に変えるかもしれない定数は
マクロ として定義しておくと見通しが良くなる。たとえば、
成績処理のプログラム listup.c
では、
最大処理人数として300人を想定し、以下のように配列宣言した。
/* 1行は100バイト、氏名は50バイトあればいいだろう */ char buffer[100], name[300][50]; /* nameは 50バイト×300人分 */ int point[300]; /* 得点も300人分 */
しかしこれでほんとうに十分だろうか。もし 寿限無さんが入学してきたらどうしよう。
(C言語に限らず)一般的に、プログラムで扱えるデータの上限を決めたりする場合は
数値そのものを書くのではなく、定数を表す変数に置き換えておく。
Cプリプロセッサ(cpp
)では、このために
#define
というディレクティブが利用できる。
#define
- プリプロセッサ・ディレクティブ
ある単語に別の意味を定義する。
#define 単語 定義内容
のように書くと、それ以後 単語 がプログラム中に 現れた場合それを 定義内容 に置き換える。
単語 の部分には、C言語の一般変数と同じ文字種が 使えるが、分かりやすくするため慣習的に大文字 とアンダースコアを組み合わせて使う。
このように単語を別の意味に置き換えるような定義のことを マクロ定義 という。
成績処理プログラムの場合、名前を格納するバッファ長を定数化しておき、 配列宣言も書き換える。
#define NAME_LEN 250 #define BUFF_LEN 280 int main() { char buffer[BUFF_LEN], name[300][NAME_LEN]; int point[300]; /* 得点300人分 */ : :
同様に、最大処理人数も「300」人と数値そのままで書くのをやめる。
#define NAME_LEN 250 #define BUFF_LEN 280 #define MAX_NINZU 300 int main() { char buffer[BUFF_LEN], name[MAX_NINZU][NAME_LEN]; int point[MAX_NINZU]; /* 得点 MAX_NINZU 人分 */ : :
もう一歩進めよう。上記の定義で
氏名の最大見積り NAME_LEN
250
1行入力の最大見積り BUFF_LEN
280
は、それぞれ無関係ではない。読み込みデータ1行の中には必ず氏名が
含まれるので NAME_LEN < BUFF_LEN
でなければならない。
〜〜〜〜バッファ全体(280バイト)〜〜〜〜 氏名相当分(250バイト) 得点相当分(残り)
このような場合は、
バッファ全体の長さは氏名の長さ見積りより10バイトくらい大きけりゃ いいんじゃないの?
のように考えるだろう。そのように考えると、バッファ長
BUFF_LEN
のマクロ定義は以下のように変えるのが望ましい。
#define NAME_LEN 250
#define BUFF_LEN (NAME_LEN+10)
マクロ定義の本体に式を使う場合は、必ず全体を括弧で括る。
もし、括弧で括らずNAME_LEN+10
とした場合を考えてみる。
実際にプログラム本体で
int big_buffer[BUFF_LEN*3];
のように利用されるとき、BUFF_LEN*3
は以下のように
展開される。
BUFF_LEN*3 ↓ NAME_LEN+10*3 ↓ 250+10*3 ↓ 280
本来、BUFF_LEN
全体の3倍、のつもりで宣言したものが
違う結果になってしまう。これを防ぐため、マクロ定義の本体の式は
全体を括弧で括る。
BUFF_LEN*3 ↓ (NAME_LEN+10)*3 ↓ (250+10)*3 ↓ 780
以上を考慮して、全員分の成績データを記憶して読み込むプログラムは 以下のように書き換えられる。
#include <stdio.h> #define NAME_LEN 250 /* 氏名の長さ最大見積り */ #define BUFF_LEN (NAME_LEN+10) /* バッファの長さ */ #define MAX_NINZU 300 /* 最大処理可能人数 */ int main() { /* 1行はBUFF_LENバイト、氏名はNAME_LENバイトあればいいだろう */ char buffer[BUFF_LEN], name[MAX_NINZU][NAME_LEN]; int point[MAX_NINZU]; /* 得点も人数分確保 */ int n=0; /* 今何人目? (0から) */ int i; /* ループ用変数 (最初に定義すべし) */ while (NULL != fgets(buffer, sizeof buffer, stdin)) { if (2 == sscanf(buffer, "%s %d", name[n], &point[n])) n++; /* nを1増やす */ } } puts("全部読み終わったよ!"); /* では改めて最初から表示*/ /* この時点で n は最後の配列要素より1大きくなっている */ for (i=0; i<n; i++) { printf("%d 番目: %s さんは %d 点\n", i+1, name[i], point[i]); } puts("以上"); }
ただし、プログラムのセキュリティを考えると今一歩及ばない。
sscanf
のtところで読み取りフォーマットを "%s %d"
としているので、名前の部分に251文字以上入れられるとname
配列はあふれてしまう。そこで、sscanf
で解析に使う
フォーマット文字列も、NAME_LEN
から自動的に修正する
ように変える。これを直したものが以下のプログラムである。
#include <stdio.h> #define NAME_LEN 250 /* 氏名の長さ最大見積り */ #define BUFF_LEN (NAME_LEN+10) /* バッファの長さ */ #define MAX_NINZU 300 /* 最大処理可能人数 */ int main() { /* 1行はBUFF_LENバイト、氏名はNAME_LENバイトあればいいだろう */ char buffer[BUFF_LEN], name[MAX_NINZU][NAME_LEN]; char sfmt[10]; int point[MAX_NINZU]; /* 得点も人数分確保 */ int n=0; /* 今何人目? (0から) */ int i; /* ループ用変数 (最初に定義すべし) */ sprintf(sfmt, "%%%ds %%d", NAME_LEN-1); puts(sfmt); while (NULL != fgets(buffer, sizeof buffer, stdin)) { if (2 == sscanf(buffer, sfmt, name[n], &point[n])) n++; /* nを1増やす */ } puts("全部読み終わったよ!"); /* では改めて最初から表示*/ /* この時点で n は最後の配列要素より1大きくなっている */ for (i=0; i<n; i++) { printf("%d 番目: %s さんは %d 点\n", i+1, name[i], point[i]); } puts("以上"); }
sprintf
関数は、printf
関数に似ているが、
書式を整えた結果を出力するのではなく、第1引数で指定した文字列
に書き込む。たとえば、
printf("%%%ds %%d", NAME_LEN-1);
とすると、
"%249s %d"
と出力される(%%
の部分は%
文字自身に
置き換わる)。
sscanf
はこれと同じ文字列を
sfmt
変数に書き込む。そのため、その下でsscanf
用のフォーマット文字列としてsfmt
変数の値を利用できる。