プリプロセッサ

導入

C 言語コンパイラは、プログラムをコンパイルする前にプリプロセッサを使用してコードを処理します。

プリプロセッサはまずコードをクリーンアップし、コメントを削除し、複数行のステートメントを 1 つの論理行に結合します。次に、「#」で始まる前処理命令を実行します。この章ではC言語の前処理ディレクティブについて紹介します。

前処理命令はプログラム内のどこにでも出現できますが、慣例によりコードの先頭に配置されることがよくあります。

各前処理ディレクティブは「#」で始まり、行の先頭に配置されます。ディレクティブの前に空白文字 (スペースやタブなど) を含めることができます。 「#」とディレクティブの残りの部分の間にスペースを入れることもできますが、古いコンパイラとの互換性を保つため、通常はスペースは残されません。

行末にバックスラッシュを使用してラップしない限り、すべての前処理ディレクティブは 1 行に記述されます。ディレクティブの末尾にセミコロンは必要ありません。

#定義する

#define は最も一般的な前処理ディレクティブで、指定された単語を別の単語に置き換えるのに使用されます。そのパラメータは 2 つの部分に分かれており、最初のパラメータが置き換えられる部分であり、残りのパラメータが置き換えられる内容です。各置換ルールはマクロと呼ばれます。

#最大100を定義

上記の例では、#define はソース コード内のすべての MAX100 に置き換えることを指定しています。 MAX はマクロと呼ばれます。

マクロ名にはスペースを使用できません。また、C 言語の変数命名規則に準拠する必要があります。使用できるのは文字、数字、アンダースコア (_) だけであり、最初の文字を数字にすることはできません。

マクロはそのまま置き換えられます。どのような内容を指定しても、まったく同じ内容に置き換えられます。

#define HELLO 「こんにちは、世界」

// printf("%s", "Hello, world"); と同等です。
printf("%s", こんにちは);

上の例では、マクロ HELLO"Hello, world" に置き換えられます。

#define ディレクティブはソース コード ファイル内のどこにでも使用でき、ディレクティブが表示された場所からファイルの最後まで有効です。ソースコードファイルの先頭に「#define」を置くのが一般的です。その主な利点は、プログラムが読みやすくなり、変更が容易になることです。

#define ディレクティブは # で始まり改行文字で終わります。命令全体が長すぎる場合は、改行でバックスラッシュを使用して次の行に進むことができます。

#define OW "C プログラミング言語が発明されました \
1970年代に。」

上の例では、最初の行の末尾にあるバックスラッシュにより、#define ディレクティブが 2 行に分割されます。

#define は複数の置換を許可します。つまり、1 つのマクロに別のマクロを含めることができます。

#define 2 2
#define 4 2*2

上の例では、「FOUR」が「2*2」に置き換えられます。

マクロが文字列内 (つまり、二重引用符内) にある場合、または別の識別子の一部である場合、マクロは無効になり、置換は行われないことに注意してください。

#define 2 2

// 2 を出力します
printf("TWO\n");

// 出力 22
const 2 = 22;
printf("%d\n", TWO);

上の例では、二重引用符内の TWO および識別子 TWOs は置換されません。

同じ名前のマクロは、定義内容が同じであれば繰り返し定義しても問題ありません。定義が異なる場合はエラーが報告されます。

// 正しい
#define FOO こんにちは
#define FOO こんにちは

// エラーを報告する
#define BAR こんにちは
#BARワールドを定義する

上記の例では、マクロ FOO は変更されていないため、繰り返し定義できます。マクロ BAR が変更されると、エラーが報告されます。

パラメータ付きマクロ

基本的な使い方

マクロの利点は、名前の後に括弧を使用して、1 つ以上のパラメーターを受け入れることを指定できることです。

#define SQUARE(X) X*X

上の例では、マクロ SQUARE はパラメータ X を受け入れることができ、これは X*X に置き換えることができます。

マクロ名と左括弧の間にスペースを入れることはできないことに注意してください。

このマクロの使い方は以下の通りです。

// z = 2*2 に置き換えます。
z = 平方(2);

この書き方は関数に非常に似ていますが、これは関数ではなく完全な置き換えであり、関数とは異なる動作をします。

#define SQUARE(X) X*X

// 出力 19
printf("%d\n", SQUARE(3 + 4));

上記の例では、SQUARE(3 + 4) が関数の場合、出力は 49 (7*7) になるはずですが、マクロはそのまま置き換えられるため、3 + 4*3 + に置き換えられます。 4、最終出力は 19 です。

ご覧のとおり、そのまま置き換えると予期しない動作が発生する可能性があります。解決策は、マクロを定義するときにできるだけ多くの括弧を使用することで、多くの事故を回避できます。

#define SQUARE(X) ((X) * (X))

上の例では、置換された形式の SQUARE(X) には 2 層のかっこがあり、これにより多くのエラーを回避できます。

マクロパラメータを空にすることもできます。

#define getchar() getc(stdin)

上の例では、マクロ getchar() のパラメータは空です。この場合、実際には括弧は省略できますが、括弧を追加するとより関数らしくなります。

一般的に、パラメータを含むマクロは 1 行に記述されます。以下に 2 つの例を示します。

#define MAX(x, y) ((x)>(y)?(x):(y))
#define IS_EVEN(n) ((n)%2==0)

マクロの長さが長すぎる場合は、バックスラッシュ (「\」) を使用してマクロを複数行にまとめることができます。

#define PRINT_NUMS_TO_PRODUCT(a, b) { \
  int 積 = (a) * (b);
  for (int i = 0; i < product; i++) { \
    printf("%d\n", i);
  } \
}

上の例では、置換テキストが中括弧内に配置されてブロック スコープが作成され、マクロ内の変数が外部を汚染するのを防ぎます。

パラメータを含むマクロは、1 つのマクロに別のマクロを含めてネストすることもできます。

#define QUADP(a, b, c) ((-(b) + sqrt((b) * (b) - 4 * (a) * (c))) / (2 * (a)))
#define QUADM(a, b, c) ((-(b) - sqrt((b) * (b) - 4 * (a) * (c))) / (2 * (a)))
#define QUAD(a, b, c) QUADP(a, b, c), QUADM(a, b, c)

上記の例は、1 変数の二次方程式を解くためのマクロです。正と負の 2 つの解があるため、最初にマクロ QUAD が他の 2 つのマクロ QUADPQUADM に置き換えられ、後者が置き換えられます。それぞれがソリューションに置き換えられます。

では、パラメータを含むマクロをいつ使用するのか、また関数をいつ使用するのか?

一般に、関数はより強力で理解しやすいため、最初に使用する必要があります。マクロは予期しない置換結果を生成することがあります。多くの場合、改行文字をエスケープしない限り 1 行でしか記述できませんが、可読性は非常に低くなります。

マクロの利点は、マクロが本質的に文字列置換であり、データ型を定義する必要がある関数とは異なり、データ型を必要としないことです。さらに、マクロはすべてを実際のコードに置き換え、関数呼び出しのオーバーヘッドを排除するため、パフォーマンスが向上します。さらに、前のコードでは多くのマクロ、特に単純な数学的演算が使用されていました。前のコードを理解するには、それを理解する必要があります。

# 演算子、## 演算子

マクロにはデータ型が関与しないため、さまざまな型の値が置換される可能性があります。置換値を文字列にしたい場合は、置換テキストのパラメータの前に「#」を追加します。

#define STR(x) #x

// printf("%s\n", "3.14159"); と同等です。
printf("%s\n", STR(3.14159));

上記の例では、「STR(3.14159)」は「3.14159」に置き換えられます。 x の前に # がない場合は、浮動小数点数として解釈され、# を付けると文字列に変換されます。

別の例を示します。

#define XNAME(n) "x"#n

// 出力x4
printf("%s\n", XNAME(4));

上記の例では、#n はパラメーター出力が文字列であることを指定し、前の文字列と結合されると、最終出力は "x4" になります。 # を付けないと、ここでの実装が非常に面倒になります。

置換されたテキスト内のパラメータを他の識別子と接続して新しい識別子を形成する必要がある場合は、## 演算子を使用できます。これは接着剤として機能し、パラメーターを識別子に「埋め込み」ます。

#define MK_ID(n) i##n

上の例では、「n」はマクロ「MK_ID」のパラメータです。このパラメータは識別子「i」と結合する必要があります。この場合、「i」と```の間に「##」演算子を使用する必要があります。ん。以下は、このマクロの使用方法の例です。

int MK_ID(1)MK_ID(2)MK_ID(3);
// に置き換えます
int i1、i2、i3;

上記の例では、置換されたテキスト 'i1'、'i2'、'i3' が 3 つの識別子であり、パラメーター 'n' は識別子の一部です。この例からわかるように、## 演算子の主な用途の 1 つは、変数名と識別子をバッチで生成することです。

不定パラメータを持つマクロ

マクロのパラメータは不定の数にすることもできます (つまり、パラメータがいくつあるかは不明です)。「...」は残りのパラメータを表します。

#define X(a, b, ...) (10*(a) + 20*(b)), __VA_ARGS__

上記の例では、「X(a, b, ...)」は、「X()」に少なくとも 2 つのパラメータがあり、追加のパラメータは「...」で表されることを意味します。置換テキストでは、__VA_ARGS__ は冗長な引数を表します (各引数の間はカンマで区切られています)。以下に使用例を示します。

X(5, 4, 3.14, "こんにちは!", 12)
// に置き換えます
(10*(5) + 20*(4))、3.14、「こんにちは!」、12

... はマクロの末尾パラメータのみを置き換えることができ、次のように記述することはできないことに注意してください。

// エラーを報告する
#define WRONG(X, ..., Y) #X #__VA_ARGS__ #Y

上の例では、中間部分のパラメータを「...」で置き換えていますが、これは許可されていないため、エラーが報告されます。

__VA_ARGS__ の前に # 記号を追加すると、出力を文字列に変えることができます。

#define X(...) #__VA_ARGS__

printf("%s\n", X(1,2,3)); // "1, 2, 3" を出力します。

#undef

#undef ディレクティブは、#define を使用して定義されたマクロをキャンセルするために使用されます。

#define LIMIT 400
#undef リミット

上記の例の undef 命令は、すでに定義されているマクロ LIMIT をキャンセルし、後で LIMIT を使用してマクロを再定義できます。

マクロを再定義したい場合がありますが、それが以前に定義されているかどうかがわからない場合は、最初に #undef を使用してマクロをキャンセルしてから、再度定義することができます。なぜなら、同じ名前のマクロを2回異なる定義をするとエラーが報告されますが、#undefのパラメータが存在しないマクロの場合はエラーが報告されないからです。

GCC の -U オプションはコマンドラインでマクロの定義を解除できます。これは #undef と同等です。

$ gcc -ULIMIT foo.c

上の例の -U パラメータはマクロ LIMIT をキャンセルします。これはソース ファイルの #undef LIMIT に相当します。

#含む

#include ディレクティブは、コンパイル中に他のソース コード ファイルを現在のファイルにロードするために使用されます。 2 つの形式があります。

//フォーム1
#include <foo.h> //システムが提供するファイルをロードします

//フォーム2
#include "foo.h" //ユーザーが提供したファイルをロードします

最初の形式では、ファイル名が山かっこで囲まれ、ファイルがシステムによって提供されること (通常は標準ライブラリのライブラリ ファイル) であることを示し、パスを記述する必要はありません。コンパイラはシステムによって指定されたインストール ディレクトリに移動してこれらのファイルを検索するためです。

2 番目の形式では、ファイル名が二重引用符で囲まれており、ファイルがユーザーによって指定されることを示します。特定のパスは、プロジェクトの現在のディレクトリまたは作業ディレクトリである可能性があります。含めるファイルが別の場所にある場合は、パスを指定する必要があります。例を次に示します。

#include "/usr/local/lib/foo.h"

GCC コンパイラの -I パラメータを使用して、include コマンドでユーザー ファイルのロード パスを指定することもできます。

$ gcc -Iinclude/ -o code code.c

上記のコマンドで、-Iinclude/ は、現在のディレクトリの include サブディレクトリからユーザー自身のファイルをロードすることを指定します。

#include の最も一般的な使用法は、関数プロトタイプ (接尾辞 .h) を含むヘッダー ファイルをロードすることです。「複数ファイルのコンパイル」の章を参照してください。複数の #include ディレクティブの順序は重要ではありません。同じヘッダー ファイルを複数回インクルードすることは合法です。

#if...#endif

#if...#endif ディレクティブは、プリプロセッサの条件判定に使用されます。条件が満たされた場合、内部行はコンパイルされ、そうでない場合はコンパイラによって無視されます。

#0の場合
  const double pi = 3.1415; // 実行されません。
#endif

上の例では、#if の後の 0 は判定条件が真でないことを意味します。したがって、内部変数定義ステートメントはコンパイラによって無視されます。 「#if 0」はコメントとして使用されることが多く、「#if 0」の中に不要なコードが配置されています。

#if 以降の判定条件は通常式です。式の値が 0 に等しくない場合は、判定条件が true であることを意味し、式の値が 0 に等しい場合は、判定条件が false であることを意味します。そして内部のステートメントは無視されます。

また、「#if...#endif」の間に「#else」ディレクティブを追加して、判定条件が真でない場合にコンパイルする必要がある文を指定することもできます。

#FOO 1 を定義

#if FOO
  printf("定義済み\n");
#それ以外
  printf("定義されていません\n");
#endif

上記の例では、マクロ「FOO」が定義されている場合は「1」に置き換えられ、「定義済み」が出力され、そうでない場合は「未定義」が出力されます。

判定条件が複数ある場合は、#elifコマンドを追加することもできます。

#if HAPPY_FACTOR == 0
  printf("不満です!\n");
#elif HAPPY_FACTOR == 1
  printf("私はただの普通です\n");
#それ以外
  printf("とても嬉しいです!\n");
#endif

上記の例では、第 2 段階の判定を #elif で指定しています。 #elif の位置は #else より前になければならないことに注意してください。複数の判定条件を満たさない場合は#else部分が実行されます。

定義されていないマクロは「0」と同等です。したがって、UNDEFINED が未定義マクロの場合、#if UNDEFINED は false、#if !UNDEFINED は true になります。

#if の一般的な用途は、デバッグ モードをオン (またはオフ) にすることです。

#define デバッグ 1

#if デバッグ
printf("i の値: %d\n", i);
printf("j の値: %d\n", j);
#endif

上記の例では、DEBUG1に設定することでデバッグモードがオンになり、デバッグ情報を出力できるようになります。

GCC の -D パラメータはコンパイル時にマクロの値を指定できるため、デバッグ スイッチを簡単にオンにすることができます。

$ gcc -DDEBUG=1 foo.c

上記の例では、-D パラメータはマクロ DEBUG1 として指定します。これは、コード内で #define DEBUG 1 を指定するのと同じです。

#ifdef...#endif

#ifdef...#endif ディレクティブは、マクロが定義されているかどうかを判断するために使用されます。

ソース コード ファイルが特定のライブラリを繰り返しロードすることがあります。この状況を回避するには、#define を使用してライブラリ ファイルに空のマクロを定義します。このマクロにより、ライブラリ ファイルがロードされているかどうかを判断します。

#define EXTRA_HAPPY

上の例では、「EXTRA_HAPPY」は空のマクロです。

次に、ソースコードファイルは #ifdef...#endif を使用してマクロが定義されているかどうかを確認します。

#ifdef エクストラ_ハッピー
  printf("とても嬉しいです!\n");
#endif

上記の例では、#ifdef はマクロ EXTRA_HAPPY が定義されているかどうかをチェックします。すでに存在する場合は、ライブラリ ファイルがロードされていることを意味し、プロンプトが 1 行出力されます。

#ifdef#else ディレクティブと一緒に使用できます。

#ifdef エクストラ_ハッピー
  printf("とても嬉しいです!\n");
#それ以外
  printf("私はただの普通です\n");
#endif

上記の例では、マクロ「EXTRA_HAPPY」が定義されていない場合、「#else」部分が実行されます。

#ifdef...#else...#endif を使用して条件付きロードを実装できます。

#ifdef メイビス
  #include "foo.h"
  #define 厩舎 1
#それ以外
  #include "bar.h"
  #define 厩舎 2
#endif

上記の例では、マクロ MAVIS が定義されているかどうかを判断することによって、異なるヘッダー ファイルがロードされます。

定義された演算子

前のセクションの #ifdef ディレクティブは #if Definition と同等です。

#ifdef FOO
// と同等
#FOO が定義されている場合

上の例では、「define」は前処理演算子で、パラメータが定義されたマクロの場合は 1 を返し、それ以外の場合は 0 を返します。

この構文を使用すると、複数の判定を完了できます。

#FOO が定義されている場合
  x = 2;
#elif 定義の BAR
  x = 3;
#endif

このオペレーターのアプリケーションの 1 つは、異なるアーキテクチャーのシステムに異なるヘッダー ファイルをロードすることです。

#定義されている場合 IBMPC
  #include "ibmpc.h"
#elif 定義MAC
  #include "mac.h"
#それ以外
  #include "general.h"
#endif

上記の例では、異なるアーキテクチャのシステムは、対応するマクロを定義する必要があります。このコードは、さまざまなマクロに従って、対応するヘッダー ファイルをロードします。

#ifndef...#endif

#ifndef...#endif ディレクティブは、#ifdef...#endif の逆です。マクロが定義されていないかどうかを判断し、指定された操作を実行するために使用されます。

#ifdef エクストラ_ハッピー
  printf("とても嬉しいです!\n");
#endif

#ifndef エクストラ_ハッピー
  printf("私はただの普通です\n");
#endif

上記の例では、マクロ EXTRA_HAPPY が定義されているかどうかに応じて、#ifdef#ifndef でそれぞれの場合にコンパイルする必要があるコードを指定します。

#ifndef は繰り返しロードを防ぐためによく使用されます。たとえば、ヘッダー ファイル myheader.h が繰り返しロードされるのを防ぐために、ヘッダー ファイルを #ifndef...#endif でロードできます。

#ifndef MYHEADER_H
  #defineMYHEADER_H
  #include "myheader.h"
#endif

上記の例では、マクロ「MYHEADER_H」はファイル名「myheader.h」の大文字に対応します。 #ifndef がこのマクロが定義されていないことを検出する限り、ヘッダー ファイルがロードされていないことを意味するため、内部コードがロードされ、再度ロードされないようにマクロ MYHEADER_H が定義されます。

#ifndef#if !define と同等です。

#ifndef FOO
// と同等
#if !定義された FOO

定義済みマクロ

C 言語には、直接使用できる定義済みマクロがいくつか用意されています。

  • __DATE__: コンパイル日。「Mmm dd yyyy」形式の文字列 (2021 年 11 月 23 日など)。
  • __TIME__: コンパイル時間 ("hh:mm:ss" の形式)。
  • __FILE__: 現在のファイル名。
  • __LINE__: 現在の行番号。
  • __func__: 現在実行されている関数の名前。この定義済みマクロは関数スコープで使用する必要があります。
  • __STDC__: 1 に設定すると、現在のコンパイラが C 標準に従っていることを示します。
  • __STDC_HOSTED__: 1 に設定されている場合は、現在のコンパイラが完全な標準ライブラリを提供できることを意味します。それ以外の場合は、0 に設定されています (組み込みシステムの標準ライブラリは不完全であることがよくあります)。
  • __STDC_VERSION__: コンパイルに使用される C 言語のバージョン。yyyymmL 形式の Long 整数です。C99 のバージョンは「199901L」、C11 のバージョンは「201112L」、C17 のバージョンは「201710L」です。 。

次の例では、これらの事前定義マクロの値を出力します。

#include <stdio.h>

int main(void) {
  printf("この関数: %s\n", __func__);
  printf("このファイル: %s\n", __FILE__);
  printf("この行: %d\n", __LINE__);
  printf("コンパイル日: %s %s\n", __DATE__, __TIME__);
  printf("C バージョン: %ld\n", __STDC_VERSION__);
}

/* 出力は次のようになります

この関数: メイン
このファイル: test.c
この行: 7
編集日: 2021 年 3 月 29 日 19:19:37
C バージョン: 201710

*/

#ライン

#line ディレクティブは、事前定義マクロ __LINE__ をオーバーライドし、カスタム行番号に変更するために使用されます。後続の行は、__LINE__ の新しい値からカウントされます。

//次の行の行番号を300にリセット
#300行目

上記の例では、「#line 300」の直後の行番号が 300 に変更され、以降の行は 300 を基準にインクリメンタルに番号が付けられます。

#line は、事前定義マクロ __FILE__ を変更してカスタム ファイル名に変更することもできます。

#300行目「新しいファイル名」

上の例では、次の行の行番号は「300」に、ファイル名は「newfilename」にリセットされます。

#エラー

#error ディレクティブは、プリプロセッサにエラーをスローさせてコンパイルを終了させるために使用されます。

#if __STDC_VERSION__ != 201112L
  #errorC11 ではありません
#endif

上記の例では、コンパイラが C11 標準を使用しない場合にコンパイルが中止されることを指定しています。 GCC コンパイラは次のようなエラーを報告します。

$ gcc -std=c99 newish.c
newish.c:14:2: エラー: #error C11 ではありません

上の例では、GCC は C99 標準を使用してコンパイルし、エラーを報告します。

#if INT_MAX < 100000
  #error int 型が小さすぎます
#endif

上記の例では、コンパイラは、INT 型の最大値が 100,000 未満であることを検出すると、コンパイルを停止します。

#error ディレクティブは、#if...#elif...#else 部分でも使用できます。

#WIN32 が定義されている場合
  // ...
#elif 定義の MAC_OS
  // ...
#elif 定義LINUX
  // ...
#それ以外
  #error オペレーティング システムをサポートしていません
#endif

#プラグマ

#pragma ディレクティブは、コンパイラのプロパティを変更するために使用されます。

// C99 標準を使用する
#プラグマ c9x オン

上記の例は、C99 標準にコンパイルするようにコンパイラーに指示します。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0