一般的に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_LEN2501行入力の最大見積り BUFF_LEN280
は、それぞれ無関係ではない。読み込みデータ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変数の値を利用できる。