プリプロセッサは、 ソースプログラム中から 翻訳には関係ないコメントを取り除くほか、 ソースプログラム中のプリプロセッサ制御文をもとに ソースプログラムを加工し、 それをコンパイラ本体に引き継ぎます。
プリプロセッサの制御は、 ソースプログラムの第1桁目に # を書くことによって行います。
プリプロセッサはソースプログラム中で上記の指令に出合うと、 ソースプログラムのその部分から後に含まれる 全ての SIZE という文字列を、 1024 に置き換えます。#define SIZE 1024
#define NINSU 100
unsigned char tensu[NINSU];
float heikin(void)
{
int i;
float sum = 0;
for (i = 0; i < NINSU; i++)
sum += tensu[i];
return sum / NINSU;
}
上の関数 heikin は、
配列 tensu に 100 人の点数データが入っているとき、
その平均点を計算するものであるとします。
ここで仮に NINSU という記号定数を使わずに 100 という定数を
そのまま書いたとすると、
データの数が変わったとき3箇所の 100 を変更しなければなりません。
これをあらかじめ記号定数で書いておけば、
そういう変更のときも記号定数の定義のところだけを
変更するだけで済みます。
上記のように記号定数に割り当てる値を省略した場合、 その記号定数の内容は空になります。#define DEBUG
記号定数の定義を取り消すには、#undef を使います。
記号定数の名前の決め方のルールは一般の変数と同じですが、 変数と区別しやすいように全部大文字にするなどの 配慮をしたほうがいいでしょう。#undef NINSU #undef DEBUG
■課題54■
記号定数は引数を使うこともできます。
#define SWAP(type, a, b) { type t = a; a = b; b = t; }
void func(float x[], float y[], int n)
{
int i;
for (i = 0; i < n; i++)
SWAP(float, x[i], y[i]);
この SWAP は関数で定義した場合と異なり、
引数 type に引数 a, b に指定した変数の型を指定することで、
任意の型の変数の内容の交換ができます。
#define EXPR(c) ((c) * 5 + 3)
int func(int x)
{
return EXPR(x + 1) * 5;
}
この EXPR は式の中で使うことができます。
ここで EXPR(x + 1) * 5 は ((x + 1) * 5 + 3) * 5
に置き替わります。
ここで注意すべきことは、 引数および定義全体が ( ) でくくってある点です。 仮に、次のような定義を行ったとします。
この場合 EXPR(x + 1) * 5 は (x + 1 * 5 + 3) * 5 に置き替わってしまいます。 同様に次のような定義を行った場合についても考えてみます。#define EXPR(c) (c * 5 + 3)
この場合は SQARE(x + 1) * 5 が (x + 1) * 5 + 3 * 5 に置き替わってしまいます。#define EXPR(c) (c) * 5 + 3
この他、次のような場合も注意が必要です。
このとき、SQARE(++x) は ((++x) * (++x)) に置き替わるため、 変数 x の内容は2増えてしまいます。#define SQARE(c) ((c) * (c))
この stdio.h のようなファイルもC言語のソースプログラムで、 ヘッダファイルとばれます。 ヘッダファイルには普通関数定義は書かず、 外部変数の宣言や関数プロトタイプ、マクロ定義などを書きます。 通常のCのソースプログラムと区別するために、 普通ファイル名の最後には ".h" を付けます。 stdio.h には標準入出力(standard input/output、printf/scanf など) を使うための関数プロトタイプなどが書かれています。#include <stdio.h>
#include において、 埋め込むファイル名を < > ではさんだ場合、 そのファイルを システムに最初から用意されているヘッダファイルを 集めた場所から取り出します。 情報処理センターのシステム(大半の UNIX)では、これは /usr/includeというディレクトリにあります。 上の例では、 実際には /usr/include/stdio.h というファイルが 埋め込まれます。
ヘッダファイルには stdio.h のように システムに最初から用意されているものの他に、 自分でも作ることができます。 #include において、 埋め込むファイル名を " " ではさんだ場合、 そのファイルは現在のディレクトリ (もとのソースコードのあるディレクトリ)から取り出します。
#include "myheader.h"
#if DEBUG
printf("DEBUG: x=%f\n", x);
#endif
このようにすると、
#if 〜 #endif の間は
記号定数 DEBUG が真(非0)のときのみコンパイルされます。
これはソフトウェアの開発中は
動作を確かめるプログラムを埋め込んでおきたいが、
完成したときにはその部分は除きたい場合などによく使われます。
#if の条件には 変数を含まない式を使うことができます。 これにはC言語(の本文)同様、 定数同士の四則演算や 関係演算、 論理演算、 それから ?:演算子 などを使うことができます。
#if X > 5 && X < 10
printf("Xは5より大きく10より小さい\n");
#else
printf("Xは5以下あるいは10以上\n");
#endif
#if DEBUG == 1
printf("デバッグレベル1\n")
#elif DEBUG == 2
printf("デバッグレベル2\n")
#elif DEBUG == 3
printf("デバッグレベル3\n")
#endif
#if 0
この部分は一切コンパイルされません。
従ってコメントのように使うこともできます。
でもそう言うことはあまりしません。
#endif
#else 以降の部分は
#if の条件が偽(0)のときのみ
コンパイルされます。
また #elif は
いくつかの条件判断を続けたいときに使います。
#elif を使わない場合は、
#if 〜 #endif は入れ子にする必要があります。
#if DEBUG == 1
printf("デバッグレベル1\n")
#else
# if DEBUG == 2
printf("デバッグレベル2\n")
# else
# if DEBUG == 3
printf("デバッグレベル3\n")
# endif
# endif
#endif
# が左端(1桁目)にあれば、
# の右に空白があっても構いません。
記号定数の内容ではなく、 記号定数が定義されているかどうかを調べたいときは defined(..)を使います。
#if defined(DEBUG)
printf("デバッグします\n");
#endif
#if defined(SUN) || !defined(SGI)
printf("これは SUN か、あるいは SGI でないものなら動きます\n");
#endif
なお #if defined(..) は #ifdef ..、
#if !defined(..) は #ifndef .. と書くこともできます。
#ifdef DEBUG
printf("デバッグします\n");
#endif
/* どこかで記号定数 SIZE が定義されていなければここで定義する */
#ifndef SIZE
# define SIZE 1024
#endif