ソースプログラムをコンパイルする前に、ソースプログラムに対して行われる前処理をプリプロセスという。このプリプロセスを行なうプログラムのことをプリプロセッサと呼ぶ。通常はコンパイラがプリプロセッサの機能を兼ね備えている。
C言語はプログラミングを容易にするためのマクロを利用できる。
C言語のプリプロセッサは、マクロが使われたC言語ソースコードをプリミティブなC言語ソースコードに変換するものである。
プリプロセッサの出力は内部的に使われるだけで、ファイルに保存されるわけではない。ただし、たいていのコンパイラにはプリプロセッサの結果を出力するためのオプションが用意されている。
gcc の場合、-E オプションを指定すると、プリプロセッサの結果を出力できる。
$ cat example.c
#include <stdio.h>
#define BUFSIZE 1024
int main(int argc, char *argv[]) {
printf("%d", BUFSIZE);
}
$ gcc -E example.c
#(stdio.hの内容が展開されるがここでは省略)
int main(int argc, char *argv[]) {
printf("%d", 1024);
}
$
C言語標準のプリプロセッサの他に、Oracle Pro*C/C++もプリプロセッサの一種である。
プリプロセッサに対する指令を「ディレクティブ」と呼ぶ。C言語プリプロセッサのディレクティブは、先頭にシャープ(#
)が付く。
C言語には次のようなディレクティブがある。
ディレクティブ | 説明 |
---|---|
#define | マクロを定義する。 |
#ifdef | シンボルが定義されているときに実行する。 |
#if | 式が真のときに実行する。 |
#include | ヘッダファイルをインクルードする。 |
#error | コンパイラにエラーを発生させる。 |
#warning | コンパイラに警告を発生させる。 |
#pragma | マシンやOS固有の機能をサポートする。 |
#define
ディレクティブはマクロの定義を行う。C言語では数値や文字列、数式にに名前を付けて定数を定義することができます。
#define identifier
#define identifier replacement
#define identifier (parameter) replacement
#include <stdio.h>
#define DEBUG
int main(int argc, char **argv) {
int i = 1;
#ifdef DEBUG
printf("i = %d\n", i);
#endif
}
上記プログラムの実行結果を以下に示す。
$ gcc -o example example.c
$ ./example
i = 1
#defineディレクティブの使用例を次に示す。この例では文字列と数値をマクロで定義している。
#include <stdio.h>
#define TAX_NAME "消費税"
#define TAX_RATE 0.1
void main() {
double price = 2500;
printf("%s %f\n", TAX_NAME, price * TAX_RATE);
}
上記プログラムの実行結果を以下に示す。
$ gcc -o example example.c
$ ./example
消費税 250.000000
マクロには引数を指定することができる。この例では数式をマクロで定義している。数式で使っている変数は、マクロを呼び出す際に引数として指定する。
#include <stdio.h>
#define MAX(A, B) A > B ? A : B
void main() {
printf("%d\n", MAX(1, 2));
printf("%d\n", MAX(4, 3));
}
上記プログラムの実行結果を以下に示す。
$ gcc -o example example.c
$ ./example
2
4
Cコンパイラで既に定義されているマクロがある。定義済みマクロの一覧を以下に示す。
#include <stdio.h>
int main(char argc, char **argv) {
int i = 1;
printf("%s:%d i = %d\n", __FILE__, __LINE__, i);
}
上記プログラムの実行結果を以下に示す。
$ gcc -o example example.c
$ ./example
example.c:4 i = 1
#if __STDC__
/* ANSI規格対応コンパイラ用 */
#else
/* ANSI規格非対応コンパイラ用 */
#endif
標準Cのバージョンを long int 値で表す。
バージョン | 値 |
---|---|
C89 | 未定義 |
C90 | 未定義 |
C95 | 199409L |
C99 | 199901L |
C11 | 201112L |
C17 | 201710L |
標準Cのバージョンを出力するプログラムの例を次に示す。
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("%ld\n", __STDC_VERSION__);
}
上記プログラムの実行例を次に示す。
$ gcc -o example example.c
$ ./example
201710
__STDC_VERSION__ は long int の値なので、大小比較もできる。
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199409L
/* C95 compatible source code */
#elif defined(__STDC__)
/* C89 compatible source code */
#endif
#ifdef
プリプロセッサ命令は、シンボルが定義されているときに
#ifdef
から
#endif
までのプリプロセッサ命令を実行します。
#ifdef シンボル名
/* シンボルが定義されているときに実行する */
#endif
シンボルは#define
プリプロセッサ命令で定義するか、Cコンパイラーのオプションで定義します。
シンボルが定義されていないときにプリプロセッサ命令を実行させるには、次のようにします。
#ifndef シンボル名
/* シンボルが定義されていないときに実行する */
#endif
シンボルが定義されているときと定義されていないときのプリプロセッサ命令を分けるには、次のようにします。
#ifdef シンボル名
/* シンボルが定義されているときに実行する */
#else
/* シンボルが定義されていないときに実行する */
#endif
#if
プリプロセッサ命令は、式が真のときに
#if
から
#endif
までのプリプロセッサ命令を実行します。
#if 式
/* 式が真のときに実行する */
#endif
式が偽のときにプリプロセッサ命令を実行させるには、次のようにします。
#if !式
/* 式が偽のときに実行する */
#endif
式が真のときと偽のときでプリプロセッサ命令を分けて実行させるには、次のようにします。
#if 式
/* 式が真のときに実行する */
#else
/* 式が偽のときに実行する */
#endif
条件分岐の条件式が複数ある場合は、#elif
プリプロセッサ命令を使用する。
#if 式1
/* 式1が真のときに実行する */
#elif 式2
/* 式1が偽、かつ式2が真のとき実行する */
#elif 式3
/* 式1が偽、かつ式2が偽、かつ式3が真のとき実行する */
#else
/* 式1、式2及び式3がすべて偽のとき実行する */
#endif
次のようにすることで、#ifdef プリプロセッサ命令と同じことができます。
#if defined(シンボル名)
/* シンボルが定義されているときに実行する */
#endif
また、次のようにすることで、
#ifndef
プリプロセッサ命令と同じことができます。
#if !defined(シンボル名)
/* シンボルが定義されていないときに実行する */
#endif
C言語では様々なマクロを定義したヘッダファイルが用意されています。ヘッダファイルはインクルードファイルとも呼ばれます。 このヘッダファイルを読み込むには、 #includeプリプロセッサ・ディレクティブを使用します。
#include <ファイル名>
どのようなヘッダファイルがあるかは処理系によって異なりますので、ライブラリーのマニュアルを参照してください。典型的なヘッダファイルを次に示します。
ファイル名 | 説明 |
---|---|
limits.h | 実装に依存する値に関するヘッダファイル |
stdio.h | 標準入出力に関するヘッダファイル |
signal.h | シグナルに関するヘッダファイル |
stdlib.h | 標準ライブラリに関するヘッダファイル |
string.h | 文字列操作に関するヘッダファイル |
sys/types.h | システムに依存する変数タイプに関するヘッダファイル |
iconv.h | 文字コード変換ライブラリ(iconv API)のヘッダファイル |
time.h | 時刻操作に関するヘッダファイル |
unistd.h | UNIX標準に関するヘッダファイル |
wchar.h | ワイドキャラクタに関するヘッダファイル |
C++のプリプロセッサでもC言語のヘッダファイルが利用できます。C++専用のヘッダファイルも用意されています。
ファイル名 | 説明 |
---|---|
cstddef | C言語のsys/types.hなどに相当 |
cstdio | C言語のstdio.hに相当 |
cstdlib | C言語のstdlib.hに相当 |
cstring | C言語のstring.hに相当 |
ctime | C言語のtime.hに相当 |
cwchar | C言語のwchar.hに相当 |
あらかじめ用意されているヘッダファイルだけでなく、自分で作ったヘッダファイルを読み込む(インクルードする)こともできます。
#include "ファイル名"
ヘッダファイルのファイル名拡張子は慣習的に .h
が使われていますので、自分でヘッダファイルを作成する場合もこれに習います。
自分で作成したヘッダファイルは任意のディレクトリに配置できます。ソースファイルと異なるディレクトリにヘッダファイルを配置した場合には、Cコンパイラのオプションでヘッダファイルの配置ディレクトリを指示しなければなりません。
システムから提供されているヘッダファイルは、同じヘッダーファイルを二重にインクルードしないよう工夫がされています。
たとえば、Solarisのstdio.hの場合は、次のようになっています。
#ifndef _STDIO_H
#define _STDIO_H
#ifdef __cplusplus
extern "C" {
#endif
......
#ifdef __cplusplus
}
#endif
#endif /* _STDIO_H */
_STDIO_Hというシンボルが定義されていない場合のみ、プリプロセッサの処理が行われるようになっています。次に_STDIO_Hというシンボルを定義しています。これにより、2回目以降のstdio.hのインクルードでは、2重にインクルードされることがなくなります。
Microsoft Windows (Visual C++) の場合は次のようになっています。
#ifndef _INC_STDIO
#define _INC_STDIO
#ifdef __cplusplus
extern "C" {
#endif
......
#ifdef __cplusplus
}
#endif
#endif /* _INC_STDIO */
自分でヘッダファイルを作る場合も、これに習うとよいでしょう。
#errorは、コンパイラにコンパイルエラーを発生させるプリプロセッサ命令である。
#error エラーメッセージ
#warningは、コンパイラに警告を発生させるプリプロセッサ命令である。
#warning 警告メッセージ
#pragma
プリプロセッサ命令は、ホストマシンやオペレーティングシステムに固有の機能をサポートします。たとえば、データが置かれるメモリ領域の正確な管理や、バージョン情報をプログラムコードに埋め込んだりします。
#pragma
プリプロセッサ命令は、マシンまたはオペレーティングシステム固有であり、通常コンパイラごとに使用できる機能が異なります。
#pragmra keyword
keywordに指定できるキーワードはCコンパイラによって異なる。
一度読み込まれたヘッダファイルを記憶しておき、同じヘッダファイルが再度読み込まれたときは、その読み込みを無視する。Visual C++ やgccなど、多くのコンパイラで使用できる。インクルードガードと同じ役割を果たす。
#pragma once
#pragma commentは、オブジェクトファイルや実行ファイルにコメントを書き込むプリプロセッサ命令であり、Visual C++ で使用できる。
ライブラリ(lib)に対するコメントは、リンカのオプションやリンクするライブラリを指定できる。
#pragma commentプリプロセッサ命令を使用して、リンクするライブラリを指定する例を次に示す。
#pragma comment(lib, "jvm.lib")
#pragma commentプリプロセッサ命令を使用して、リンカのオプションを指定する例を次に示す。
#pragma comment(lib, "/nologo")
Solarisのccでは、#pragma identでSCCSバージョン情報をLMFに埋め込むことができる。
#pragma ident "@(#)main.c 92/02/07"
プリプロセッサでは、マクロを置き換える演算子を使用できる。プリプロセッサで使えるマクロ置き換え演算子には次のものがある。
演算子 | 説明 |
---|---|
# | マクロ実引数を文字列化する。 |
## | 前後の字句列を結合する。 |
#演算子は、マクロ実引数を文字列化します。
#define strgen1(x) "x"
#define strgen2(x) x
#define strgen3(x) #x
char *p, *string = "abc";
p = strgen1(string); /* p = "x" */
p = strgen2(string); /* p = "abc" */
p = strgen3(string); /* p = "string" */
下記の例の場合、"main-" "func" ".c" が文字列連結されて、"main-func.c" となります。
#define PREFIX "main-"
#define SUFFIX ".c"
#define fname(name) PREFIX #name SUFFIX
p = fname(func); /* p = "main-func.c" */
##演算子は、前後の字句列を結合します。
#define symadd(x, y) sym##x + sym##y
int sym1, sym2;
i = symadd(1, 2); /* sym1 + sym2 */
Microsoft (2022) 定義済みマクロ