ソースプログラムをコンパイルする前に、ソースプログラムに対して行われる前処理を プリプロセス と呼ぶ。このプリプロセスを行なうプログラムのことを プリプロセッサ と呼ぶ。通常は、コンパイラがプリプロセッサの機能を兼ね備えている。

プリプロセッサに対する命令を ディレクティブ と呼ぶ。C言語プリプロセッサのディレクティブは、シャープ( # )が頭に付く。

プリプロセッサの出力は内部的に使われるだけで、ファイルに保存されるわけではないが、たいていのコンパイラにはプリプロセッサの出力をファイルに保存するためのオプションが用意されている。

スポンサーリンク

#define

#define ディレクティブはマクロの定義を行う。C言語では数値や文字列、数式にに名前を付けて定数を定義することができます。

#define identifier replacement-token-list
#define identifier (parameter-list) replacement-token-list

identifier にはマクロの識別子を指定する。識別子は大文字と小文字を区別する。マクロの識別子は慣習的に大文字で付けることが多い。

replacement-token-list には置き換える文字列を指定する。プリプロセッサによってマクロは対応する文字列に置き換えられる。

parameter-list には引数のリストを指定する。

#include <stdio.h>

#define TAX_NAME "消費税"
#define TAX_RATE  0.05

void main() {
    double price = 2500;

    printf("%s %f\n", TAX_NAME, price * TAX_RATE);
}
#include <stdio.h>

#define MAX(A, B) A > B ? A : B

void main() {
    printf("%d\n", MAX(8, 4));
    printf("%d\n", MAX(3, 4));
}

既定義マクロ

Cコンパイラで既に定義されているマクロを次に示します。

既定義マクロ
識別子 説明
__FILE__ ソースファイル名 (Windows はフルパス、Solaris はファイル名のみ)
__LINE__ 行番号
__DATE__ コンパイル日付
__TIME__ コンパイル時間
__STDC__ ANSI規格対応ならば1、ANSI規格非対応ならば0
#if __STDC__
  /* ANSI規格対応コンパイラ用 */
#else
  /* ANSI規格非対応コンパイラ用 */
#endif
if (answer != 1000)
  printf("answer = %d %s:%d\n", answer, __FILE__, __LINE__);
answer = 99 compute.c:237

#ifdef

#ifdef プリプロセッサ命令は、シンボルが定義されているときに #ifdef から #endif までのプリプロセッサ命令を実行します。

#ifdef シンボル名
シンボルが定義されているときに実行するプリプロセッサ命令
#endif

シンボルは #define プリプロセッサ命令で定義するか、Cコンパイラーのオプションで定義します。

シンボルが定義されていないときにプリプロセッサ命令を実行させるには、次のようにします。

#ifndef シンボル名
シンボルが定義されていないときに実行するプリプロセッサ命令
#endif

シンボルが定義されているときと定義されていないときのプリプロセッサ命令を分けるには、次のようにします。

#ifdef シンボル名
シンボルが定義されているときに実行するプリプロセッサ命令
#else
シンボルが定義されていないときに実行するプリプロセッサ命令
#endif

#if

#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

#include

C言語では様々なマクロを定義したヘッダファイルが用意されています。ヘッダファイルはインクルードファイルとも呼ばれます。 このヘッダファイルを読み込むには、 #includeプリプロセッサ・ディレクティブを使用します。

#include <ファイル名>

どのようなヘッダファイルがあるかは処理系によって異なりますので、ライブラリーのマニュアルを参照してください。典型的なヘッダファイルを次に示します。

C言語のヘッダファイル
ファイル名 説明
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++専用のヘッダファイルも用意されています。

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 の場合は次のようになっています。

#ifndef _STDIO_H

#define _STDIO_H

#ifdef __cplusplus
extern "C" {
#endif

......

#ifdef __cplusplus
}
#endif

#endif /* _STDIO_H */

Microsoft Windows (Visual C++) の場合は次のようになっています。

#ifndef _INC_STDIO

#define_INC_STDIO

#ifdef __cplusplus
extern "C" {
#endif

......

#ifdef __cplusplus
}
#endif

#endif /* _INC_STDIO */

自分でヘッダファイルを作る場合も、これに習うとよいでしょう。

#error

#errorは、コンパイラにコンパイルエラーを発生させるプリプロセッサ命令である。

#error エラーメッセージ

#warning

#warningは、コンパイラに警告を発生させるプリプロセッサ命令である。

#warning 警告メッセージ

#pragma

#pragma プリプロセッサ命令は、ホストマシンやオペレーティングシステムに固有の機能をサポートします。たとえば、データが置かれるメモリ領域の正確な管理や、バージョン情報をプログラムコードに埋め込んだりします。 #pragma プリプロセッサ命令は、マシンまたはオペレーティングシステム固有であり、通常コンパイラごとに使用できる機能が異なります。

#pragma once

一度読み込まれたヘッダファイルを記憶しておき、同じヘッダファイルが再度読み込まれたときは、その読み込みを無視する。Visual C++やgccなど、多くのコンパイラで使用できる。インクルードガードと同じ役割を果たす。

#pragma once

#pragma comment

#pragma commentは、オブジェクトファイルや実行ファイルにコメントを書き込むプリプロセッサ命令であり、Visual C++で使用できる。

ライブラリ(lib)に対するコメントは、リンカのオプションやリンクするライブラリを指定できる。

#pragma commentプリプロセッサ命令を使用して、リンクするライブラリを指定する例を次に示す。

#pragma comment(lib, "jvm.lib")

#pragma commentプリプロセッサ命令を使用して、リンカのオプションを指定する例を次に示す。

#pragma comment(lib, "/nologo")

#pragma ident

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 */
スポンサーリンク