UNIX/Linuxの部屋 cppコマンドの使い方

TOP UNIX/Linuxの部屋 UNIX/Linuxコマンド一覧 用語集 新版 由来/読み方辞書 環境変数マニュアル Cシェル変数 システム設定ファイル システムコール・ライブラリ ネットワークプログラミングの基礎知識 クラウドサービス徹底比較・徹底解説




コマンド cpp C言語のプリプロセッサ。コンパイルの前に #define・#include などの前処理を行う このエントリーをはてなブックマークに追加

最終更新


cpp コマンドは C 言語のソースを解析し、#define、#include、#ifdef、sizeof などの処理を行う。通常 C コンパイラから自動的に呼ばれる。



cpp コマンドの基本的な使い方
下記のような C のソース sample.c があるとする。
#define MAXLOOP 5
#define MESSAGE "Hello world!"
#include <stdio.h>
int main(){
int i;
for ( i=0 ; i<MAXLOOP ; i++) {
puts(MESSAGE);
}
return 0;
}

Cプリプロセッサ cpp は、#deine や #include を処理するためのコマンドである。通常は
% gcc -o sample sample.c
または
% cc -o sample sample.c
とすると、プリプロセス→コンパイル→アセンブル→リンク という過程を経て、実行可能なバイナリファイル sample が生成される。この「プリプロセス」を担当するのが cpp コマンドである。
% gcc -E sample.c > sample.i
または
% cc -E sample.c > sample.i
とすることで、プリプロセスで処理を止め、その時点での結果をファイルに出力することができる。

これは cpp コマンドを単体で
% cpp sample.c > sample.i
としても同じ結果になる。

一般的なプリプロセッサが解釈できるマクロや条件式を以下に紹介する。

#define
マクロを定義する。いくつか使い方があるが、まずは数値の定義から。
#define BUFLEN 256
char buf[BUFLEN];
#define PERSON_MAX 10
for ( i=0 ; i<PERSON_MAX ; i++ ) ...
#define M_PI 3.14159265358979323846

関数の別名定義。
#define MYFUNC0 hoge
#define MYFUNC1(x) hoge(x)
#define MYFUNC2(x,y) hoge(x,y)
#define MYFUNC3(x) hoge(x,(x)+1)
MYFUNC3 のように引数で受けた値を複数回使用する場合は注意。もし MYFUNC3(a++) とすると hoge(a++, (a++)+1) と展開されてしまう。

条件判断。
#define IS_HOGE(x) (0 < (x) && (x) < 256)
if ( IS_HOGE(i) ) ...
if ( IS_HOGE(i+1) ) ...

複数の実行文 (マルチステートメント) をまとめる。
#define MYFUNC(x, y) do { func(x); func(y); } while(0) /* ここにセミコロンを付けないこと */
一見、do 〜 while ループを取り去って
#define MYFUNC(x, y) { func(x); func(y); }
としてもよいように思えるが、これだと
if (hoge)
MYFUNC(1,2);
else
printf("hoge");
としたときに文法エラーとなってしまう。

#ifdef の使用を前提とした #define。以下の例でデバッグモードにしたくないときは、#define DEBUG_MODE の行をコメント化すればよい
#define DEBUG_MODE
#ifdef DEBUG_MODE
fprintf(stderr, "foo=%d\n", foo);
#endif

多重 include を避けるための #define。同じインクルードファイルを何度も include してしまい、マクロ再定義などの警告が発生することがある。しかしファイルの数が多くて依存関係が複雑なとき、一度しか include しないようにするのは至難の技だ。そういうときは以下のように、何度 include されても初回しか評価されないようにすればよい。
#ifndef _FOO_H_
#define _FOO_H_
#define FOO_NUM 100
void foo_func(char *);
#endif
一回目は _FOO_H_ が定義されていないので #ifndef _FOO_H_ は偽になり、内部が評価される (そしてこのとき _FOO_H_ が定義される)。二回目以降は #ifndef _FOO_H_ が真になるため、内部はスキップされる。これは stdio.h などの標準のヘッダファイルでも広く使われている有名な技である。

なお、型の別名定義のために #define を使うと問題が発生することがある。
#define MYCHAR char
MYCHAR c;
⇒ これは問題ない (char c と置換される)
#define MYCHAR_P char *
MYCHAR_P p;
⇒ これも問題ない (char *p と置換される)
#define MYCHAR_P char *
MYCHAR_P p1,p2;
⇒ これはダメ (char *p1,p2 となってしまう。おそらく意図したのは char *p1,*p2 だろう)
型の別名は typedef を使うこと。
typedef char *MYCHAR_P;
MYCHAR_P p1,p2;
⇒ typedef を使えば OK (char *p1,*p2 と解釈される)
構造体にいちいち「struct」を付けないための typedef もよく使われる。
typedef struct {
int member1;
char *member2;
} hoge_t;
なお、定義した型には size_t・time_t・pid_t などにならって、末尾に「_t」(type の略) を付けることをお勧めする。

#if・#ifdef
バージョンによる切り分け。
#if __FreeBSD_version >= 500000

#elif __FreeBSD_version >= 400000

#else

#endif

複雑な形の #ifdef。
#if defined(__FreeBSD__) && !defined(__NetBSD__)

#endif
もし #if を使わないなら、以下のように読みづらい形になってしまう。
#ifdef __FreeBSD__
#ifndef __NetBSD__

#endif
#endif
どうしてもこのようにネストせざるをえない場合は、以下のように #endif の後にコメントを付けるとよい。
#ifdef __FreeBSD__
#ifndef __NetBSD__

#endif /* __NetBSD__ */
#endif /* __FreeBSD__ */

コメントアウト目的の #if。コメント化したい場所に /* 〜 */ というコメントが含まれている場合、/* 〜 */ で囲ってしまうとコメントがネストしてしまいエラーとなる。そのような場合は #if 0 〜 #endif で囲むとよい。なお、#if 〜 #endif はネストできるので、さらに外側に #if 0 〜 #endif を重ねてもよい。
#if 0
func1(); /* 処理 A */
func2(); /* 処理 B */
func3(); /* 処理 C */
#endif

#include
ヘッダファイルを取り込む (include する)。
#include <stdio.h>
⇒ システム標準のディレクトリ (/usr/include) から先に検索
#include "hoge.h"
⇒ カレントディレクトリを先に検索し、見つからなければシステム標準のディレクトリを探す

#error
強制的に処理を打ち切り、エラーとする。例えば FreeBSD の /usr/include/varargs.h には以下のようにある。
#if defined(__GNUC__) && (__GNUC__ == 3 && __GNUC_MINOR__ > 2 || __GNUC__ >= 4)
#error "<varargs.h> is obsolete with this version of GCC."
#error "Change your code to use <stdarg.h> instead."
#endif
⇒ gcc-3.3 以上か gcc-4 以上で varargs.h をインクルードしている場合はエラーとし、「varargs.h でなく stdarg.h を使用してね」というメッセージを表示する
gcc-3.3.3 で #include <varargs.h> を含むソースをコンパイルすると、以下のようにエラーになる。
% gcc foo.c
In file included from foo.c:1:
/usr/include/varargs.h:34:2: #error "<varargs.h> is obsolete with this version of GCC."
/usr/include/varargs.h:35:2: #error "Change your code to use <stdarg.h> instead."

定義済マクロ
ANSI C (ISO C) では以下の定義済マクロを用意している。
main(){
printf("__FILE__: %s\n", __FILE__);
printf("__LINE__: %d\n", __LINE__);
printf("__DATE__: %s\n", __DATE__);
printf("__TIME__: %s\n", __TIME__);
printf("__STDC__: %d\n", __STDC__);
printf("buf=%s\n", buf);
}
実行結果は以下の通り。
% ./a.out
__FILE__: foo.c
⇒ ソースファイル名 (cc hoge/foo.c としてコンパイルしたら __FILE__ は hoge/foo.c となる)
__LINE__: 4
⇒ ソースの行番号 (__LINE__ が置かれた場所の行番号)
__DATE__: Aug 1 2004
⇒ コンパイルを行った年月日
__TIME__: 03:43:21
⇒ コンパイルを行った時分秒
__STDC__: 1
⇒ ANSI C 準拠の環境なら 1

典型的な使い方は、エラーが発生したファイルと行番号を表示するものである。
ret = func();
if ( ! ret ){
fprintf(stderr, "%s:%d func error\n", __FILE__, __LINE__);
exit(1);
}

より洗練されたやり方は以下の通り。エラーチェックの場所に毎回 __FILE__ や __LINE__ を書く必要がない。__FILE__ や __LINE__ は #define の場所でなく、ERROR マクロを使用している場所を示す。
#define ERROR(str) fprintf(stderr, "%s:%d " str "\n", __FILE__, __LINE__);
ret = func();
if ( ! ret ){
ERROR("func error");
exit(1);
}

さらによいのは可変引数関数と可変個数引数のマクロを使った以下のやり方であろう。ERROR マクロに可変個数の引数を渡すことができ、詳細なエラー情報を出力できるからである。
#include <stdio.h>
#include <stdarg.h>
#define ERROR(fmt, ...) error(__FILE__, __LINE__, fmt, __VA_ARGS__)
void error(char *file, int line, char *fmt, ...){
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "%s:%d ", file, line);
vfprintf(stderr, fmt, ap);
fputc('\n', stderr);
va_end(ap);
}
main(){
ret = func(str);
if ( ! ret ){
ERROR("func error. ret[%d] str[%s]", ret, str);
exit(1);
}
}
なお、#define での可変個数引数 (... と __VA_ARGS__) は 1999年に成立した C99 という規格で定められたものだが、規格制定前に先行して実装していたコンパイラも多い。お手元の環境で使用可能かどうか一度試してみるとよいだろう。

その他の定義済マクロを表示するには、GNU cpp ならば -dM オプションを使う。
% cpp -dM /dev/null
#define __FreeBSD__ 3
#define __FreeBSD_cc_version 330001
#define i386 1
#define __GNUC__ 2
#define unix 1
(略)
とする。ソースファイルとして /dev/null を指定しているので、処理系独自のマクロのみが表示される。

注意点
手元の環境に C コンパイラがあるとして、必ず cpp コマンドとして存在するかは当ページ管理人としては確信が持てない。

  • gcc では cpp コマンドが存在する。
  • Clang では /usr/bin/clang-cpp が存在し、(少なくとも FreeBSD では) /usr/bin/cpp とハードリンクされている。
  • Intel C/C++ コンパイラには、独立したプリプロセッサは存在しないものの、icc -E でプリプロセス結果を出力可能なようである。

また、gcc -v sample.c や (Clang の) cc -v sample.c の結果を見ると、当ページ管理人の環境においては cpp を直接起動せず、gcc では cc1 が、Clang では cc コマンドがプリプロセスを担当しているようである。ただ、下記のように cc -E と cpp の結果は一致した。
  • gcc の場合: gcc -E sample.c と cpp sample.c の結果は一致
  • Clang の場合: cc -E sample.c と cpp sample.c の結果は一致

いろいろなプリプロセッサ
この他に言語独立なプリプロセッサとしては以下のものがある。
  • mcpp
C/C++ 対応。ソースが C90・C99 などの各規格に合致しているか検証する機能があるため、移植性の高い記述が可能。作者が日本人なので日本語ドキュメントが用意されている。ちなみに「未踏ソフトウェア採択プロジェクト」である。
  • filepp
基本的なプリプロセッサの機能に加え、ループ機能などの独自拡張を施している。
  • chaos-pp
使ったことがないので、何とも言えず。
>> Linuxオンラインマニュアル(man) Linux cpp(1)
>> FreeBSDオンラインマニュアル(man) FreeBSD cpp(1)
>> Solaris10オンラインマニュアル(man) Solaris10 cpp(1)