プリプロセッサは、 ソースプログラム中から 翻訳には関係ないコメントを取り除くほか、 ソースプログラム中のプリプロセッサ制御文をもとに ソースプログラムを加工し、 それをコンパイラ本体に引き継ぎます。
プリプロセッサの制御は、 ソースプログラムの第1桁目に # を書くことによって行います。
プリプロセッサはソースプログラム中で上記の指令に出合うと、 ソースプログラムのその部分から後に含まれる 全ての SIZE という文字列を、 1024 に置き換えます。#define SIZE 1024
上の関数 heikin は、 配列 tensu に 100 人の点数データが入っているとき、 その平均点を計算するものであるとします。 ここで仮に NINSU という記号定数を使わずに 100 という定数を そのまま書いたとすると、 データの数が変わったとき3箇所の 100 を変更しなければなりません。 これをあらかじめ記号定数で書いておけば、 そういう変更のときも記号定数の定義のところだけを 変更するだけで済みます。#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; }
上記のように記号定数に割り当てる値を省略した場合、 その記号定数の内容は空になります。#define DEBUG
記号定数の定義を取り消すには、#undef を使います。
記号定数の名前の決め方のルールは一般の変数と同じですが、 変数と区別しやすいように全部大文字にするなどの 配慮をしたほうがいいでしょう。#undef NINSU #undef DEBUG
■課題54■
記号定数は引数を使うこともできます。
この SWAP は関数で定義した場合と異なり、 引数 type に引数 a, b に指定した変数の型を指定することで、 任意の型の変数の内容の交換ができます。#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]);
この EXPR は式の中で使うことができます。 ここで EXPR(x + 1) * 5 は ((x + 1) * 5 + 3) * 5 に置き替わります。#define EXPR(c) ((c) * 5 + 3) int func(int x) { return EXPR(x + 1) * 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 〜 #endif の間は 記号定数 DEBUG が真(非0)のときのみコンパイルされます。 これはソフトウェアの開発中は 動作を確かめるプログラムを埋め込んでおきたいが、 完成したときにはその部分は除きたい場合などによく使われます。#if DEBUG printf("DEBUG: x=%f\n", x); #endif
#if の条件には 変数を含まない式を使うことができます。 これにはC言語(の本文)同様、 定数同士の四則演算や 関係演算、 論理演算、 それから ?:演算子 などを使うことができます。
#else 以降の部分は #if の条件が偽(0)のときのみ コンパイルされます。 また #elif は いくつかの条件判断を続けたいときに使います。 #elif を使わない場合は、 #if 〜 #endif は入れ子にする必要があります。#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
# が左端(1桁目)にあれば、 # の右に空白があっても構いません。#if DEBUG == 1 printf("デバッグレベル1\n") #else # if DEBUG == 2 printf("デバッグレベル2\n") # else # if DEBUG == 3 printf("デバッグレベル3\n") # endif # endif #endif
記号定数の内容ではなく、 記号定数が定義されているかどうかを調べたいときは defined(..)を使います。
なお #if defined(..) は #ifdef ..、 #if !defined(..) は #ifndef .. と書くこともできます。#if defined(DEBUG) printf("デバッグします\n"); #endif #if defined(SUN) || !defined(SGI) printf("これは SUN か、あるいは SGI でないものなら動きます\n"); #endif
#ifdef DEBUG printf("デバッグします\n"); #endif /* どこかで記号定数 SIZE が定義されていなければここで定義する */ #ifndef SIZE # define SIZE 1024 #endif