C プリプロセッサ
Cプリプロセッサ(CPP)はコンパイルプロセスにおける独立した段階であり、実際のコンパイルの前にソースコードに対してテキスト処理を行います。主な機能は次のとおりです:
- マクロ展開
- ファイルインクルード
- 条件付きコンパイル
- 特殊ディレクティブの処理
Cプリプロセッサはコンパイラの構成要素ではありませんが、コンパイルプロセスにおける独立したステップです。
簡単に言うと、Cプリプロセッサは単なるテキスト置換ツールにすぎず、実際のコンパイルの前に必要な前処理を完了するようにコンパイラに指示します。
Cプリプロセッサ(C Preprocessor)をCPPと略記します。
すべてのプリプロセッサコマンドはシャープ記号(#)で#始まります。それは最初の非空白文字でなければなりません。可読性を高めるために、プリプロセッサディレクティブは先頭列から始めるべきです。
以下に、重要なプリプロセッサディレクティブをすべて示します:
| ディレクティブ | 説明 | 使用例 |
|---|---|---|
#define |
マクロを定義する(記号定数または関数形式マクロ) | #define PI 3.14159#define MAX(a,b) ((a) > (b) ? (a) : (b)) |
#include |
ヘッダーファイルをインクルードする | #include <stdio.h>#include "myheader.h" |
#undef |
定義済みのマクロを無効化する | #undef PI |
#ifdef |
マクロが定義されている場合、後続のコードをコンパイルする | #ifdef DEBUGprintf("Debug info\n");#endif |
#ifndef |
マクロが定義されていない場合、後続のコードをコンパイルする(ヘッダーファイル保護によく使用される) | #ifndef HEADER_H#define HEADER_H/* 内容 */#endif |
#if |
条件付きコンパイル(defined演算子と組み合わせて使用可能) | #if VERSION > 2/* 新版コード */#endif |
#else |
#if/#ifdef/#ifndefの代替分岐 |
#ifdef WIN32/* Windows代码 */#else/* 他のシステム */#endif |
#elif |
else ifに似ています | #if defined(UNIX)/* Unix代码 */#elif defined(WIN32)/* Windows代码 */#endif |
#endif |
条件付きコンパイルブロックを終了する | 上記の例のように |
#error |
コンパイルエラーを発生させ、メッセージを出力する | #if !defined(C99)#error "需要C99标准"#endif |
#pragma |
コンパイラ固有のディレクティブ(非標準、コンパイラによって異なる) | #pragma once#pragma pack(1) |
プリプロセッサの例
以下の例を分析して、さまざまなディレクティブを理解しましょう。
#define MAX_ARRAY_LENGTH 20
このディレクティブは、CPPにすべてのMAX_ARRAY_LENGTHを20として定義するように指示します。#define定数を定義して可読性を高めます。
#include <stdio.h> #include "myheader.h"
これらのディレクティブは、CPPにシステムライブラリからstdio.hを取得し、テキストを現在のソースファイルに追加するように指示します。次の行は、CPPにローカルディレクトリからmyheader.hを取得し、その内容を現在のソースファイルに追加するように指示します。
#undef FILE_SIZE #define FILE_SIZE 42
このディレクティブは、CPPに定義済みのFILE_SIZEを無効化し、それを42として定義するように指示します。
#ifndef MESSAGE #define MESSAGE "You wish!" #endif
このディレクティブは、CPPにMESSAGEが定義されていない場合にのみMESSAGEを定義するように指示します。
#ifdef DEBUG /* Your debugging statements here */ #endif
このディレクティブは、CPPにDEBUGが定義されている場合に処理ステートメントを実行するように指示します。コンパイル時に、gccコンパイラに-DDEBUGスイッチを渡すと、このディレクティブは非常に便利です。DEBUGが定義されるため、コンパイル中にいつでもデバッグをオンまたはオフにできます。
例
// 定数マクロを定義
#define PI 3.1415926
#define GREETING "Hello, World!"
// 関数形式マクロを定義(括弧の使用に注意)
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// 条件付きコンパイルの例
#define DEBUG 1
int main() {
// 定数マクロを使用
printf("PIの値: %f\n", PI);
printf("%s\n", GREETING);
// 関数形式マクロを使用
int x = 5;
printf("%dの2乗は: %d\n", x, SQUARE(x));
printf("3と5のうち大きい数は: %d\n", MAX(3, 5));
// 条件付きコンパイルの例
#ifdef DEBUG
printf("[デバッグ情報] プログラムはmain関数まで実行されました\n");
#endif
// コンパイラのバージョン確認
#if __STDC_VERSION__ >= 201112L
printf("C11標準を使用\n");
#elif __STDC_VERSION__ >= 199901L
printf("C99標準を使用\n");
#else
printf("C89/C90標準を使用\n");
#endif
// エラーディレクティブの例(コメントを外すとコンパイルエラーになります)
// #error "これは手動でトリガーされたエラーです"
return 0;
}
ベストプラクティスの推奨事項
-
マクロの命名:
- マクロ名にはすべて大文字とアンダースコアを使用する
- 例:
#define MAX_SIZE 100
-
関数形式マクロの注意事項:
- 各パラメータと式全体を括弧で囲むこと
- 副作用のある引数を使用しないこと(例:
SQUARE(x++))
-
ヘッダーファイル保護:
#ifndef MY_HEADER_H #define MY_HEADER_H /* 头文件内容 */ #endif
-
条件付きコンパイルによるデバッグ:
#ifdef DEBUG #define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__) #else #define DEBUG_PRINT(fmt, ...) #endif
-
クロスプラットフォーム開発:
#if defined(_WIN32) // Windows特定代码 #elif defined(__linux__) // Linux特定代码 #elif defined(__APPLE__) // macOS特定代码 #endif
定義済みマクロ
ANSI Cは多くのマクロを定義しています。プログラミングではこれらのマクロを使用できますが、定義済みのマクロを直接変更することはできません。
| 宏 | 説明 |
|---|---|
| __DATE__ | 現在の日付。"MMM DD YYYY"形式の文字定数。 |
| __TIME__ | 現在の時刻。"HH:MM:SS"形式の文字定数。 |
| __FILE__ | 現在のファイル名を含む文字列定数。 |
| __LINE__ | 現在の行番号を含む10進定数。 |
| __STDC__ | コンパイラがANSI標準でコンパイルする場合に1と定義されます。 |
次の例を試してみましょう:
例
上記のコードが(ファイルtest.c内で)コンパイルおよび実行されると、次の結果が生成されます:
当前文件: predef_macros.c 编译日期: Jul 5 2023 编译时间: 14:30:45 当前行号: 13 ANSI标准: 1 [调试信息] predef_macros.c (第16行) 编译于 Jul 5 2023 14:30:45
プリプロセッサ演算子
Cプリプロセッサは、マクロを作成するために次の演算子を提供しています:
マクロ継続演算子(\)
マクロは通常1行で記述されます。しかし、マクロが長すぎて1行に収まらない場合は、マクロ継続演算子(\)を使用します。例:
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
文字列化演算子(#)
マクロ定義では、マクロの引数を文字列定数に変換する必要がある場合に、文字列化演算子(#)を使用します。マクロ内で使用されるこの演算子は、特定の引数または引数リストを持ちます。例:
例
上記のコードがコンパイルおよび実行されると、次の結果が生成されます:
Carole and Debra: We love you!
トークン連結演算子(##)
マクロ定義内のトークン連結演算子(##)は、2つの引数を結合します。これにより、マクロ定義内で2つの独立したトークンを1つのトークンに結合できます。例:
例
上記のコードがコンパイルおよび実行されると、次の結果が生成されます:
token34 = 40
なぜこのような結果になるのでしょうか。この例では、コンパイラから次の実際の出力が生成されるためです:
printf ("token34 = %d", token34);
この例では、token##nがtoken34に連結されることを示しています。ここでは、次の演算子を使用しました:文字列化演算子(#)とトークン連結演算子(##)。
defined() 演算子
プリプロセッサdefined演算子は定数式で使用され、識別子が#defineで定義済みかどうかを判定します。指定された識別子が定義されている場合は値は真(非ゼロ)です。指定された識別子が定義されていない場合は値は偽(ゼロ)です。次の例は、defined()演算子の使用法を示しています:
例
上記のコードがコンパイルおよび実行されると、次の結果が生成されます:
Here is the message: You wish!
引数付きマクロ
CPPの強力な機能の1つは、引数付きマクロを使用して関数をシミュレートできることです。例えば、次のコードは数値の2乗を計算します:
int square(int x) {
return x * x;
}
上のコードは、次のようにマクロを使用して書き換えることができます:
#define square(x) ((x) * (x))
引数付きマクロを使用する前に、#defineディレクティブで定義する必要があります。パラメータリストは丸括弧で囲み、マクロ名の直後に置く必要があります。マクロ名と左丸括弧の間にスペースを入れることはできません。例:
例
上記のコードがコンパイルおよび実行されると、次の結果が生成されます:
Max between 20 and 10 is 20その他の拡張