C/C++: 前処理 (パート 2)

目次

1. プログラムのコンパイルとリンクのプロセスを確認する

2. 定義済み #define の前処理

1. #define で定義された識別子

2. #define 定義済みマクロ

3. マクロ引数として副作用を持つ式 

4. 2 つの古典的なマクロ

5. #define 使用上の注意事項まとめ

6. マクロと機能の比較

7.#undef

添付: #define に関する 3 つの冷たい事実

3. 条件付きコンパイル

4. 前処理 #include

1.#include<>と#include""

2.ヘッダーファイルの重複インクルード


1. プログラムのコンパイルとリンクのプロセスを確認する

2. 定義済み #define の前処理

1. #define で定義された識別子

#define で定義された識別子は、ソース コード ファイル前処理段階定義された内容置き換えられます

例えば:

#define MAX 1000
//MAX 是被宏定义的标识符
//MAX空格后的所有内容是其定义的内容           

知らせ:

  • 識別子はスペースで終わります (つまり、#define で定義された識別子にはスペースが含まれません)。
  • 最後に #define ステートメントを追加しないでください。そうしないと、セミコロンがコード セグメントで置き換えられ、バグが発生します。

2. #define 定義済みマクロ

#define で定義された識別子はパラメーターを持つことができ (関数と同様)、#define で定義されたパラメーターを持つ識別子はマクロと呼ばれます。

例えば:

#define N 4
#define Y(n) ((N+2)*n)


z = 2 * (N + Y(5+1));  //z最后的结果是多少?
  • Y(n) はマクロ、Y はマクロ名、n はマクロのパラメータ
  • z の結果分析:

したがって、z の最終的な計算結果は 70 です。式の 5+1 が最初に計算されないことがわかります。したがって、数式を評価するために使用されるマクロ定義は、マクロ パラメーターとマクロ本体を で囲むことに注意を払う必要があります。括弧 演算子の優先順位の問題により、操作の結果が期待どおりにならないようにするために、マクロ Y(n) をより厳密に記述するには、次のようにする必要があります。

#define Y(n) (((N)+2)*(n))

3.マクロ引数 として副作用を持つ

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

最終的には: z=9, x=6, y=10. ( x は 2 回増加し、 y は 1 回増加する)

  • 副作用のある式(式が評価されると関連する変数の値が変更されるという副作用)は、マクロ引数として非常に危険であることがわかります。

4. 2 つの古典的なマクロ

  • Baidu エンジニア作成のテスト問題:最初のアドレスに相対的な構造内のメンバー変数のバイト オフセットを計算するマクロを作成します(マクロの実装) (マクロの実際のパラメーターは、変数の型名にすることができます)offsetof
#define OFFSETOF(structname,numbername) (size_t)&(((structname *)0)->numbername)

#define OFFSETOF(structname,numbername) (size_t)&(((structname *)0)->numbername)

//宏的测试
typedef struct Node
{
    int i;
    char c;
    short d;
}Node;
int main ()
{
    printf("%u\n",OFFSETOF(Node,i));
    printf("%u\n",OFFSETOF(Node,c));
    printf("%u\n",OFFSETOF(Node,d));
    return 0;
}

これは、マクロ パラメーターとして型名を使用する一般的な方法です。

構造体メンバーのオフセットについては、構造体メモリの配置を参照してください:  http://t.csdn.cn/Vd6ix

  • 古典的なアルゴリズム マクロ: 正の整数 (32 ビット) の 2 の補数の奇数ビットと偶数ビットを交換できるマクロを作成します。 

例えば:

#define EXCHANGEBIN(NUM) (((NUM)&(0x55555555))<<1)|(((NUM)&(0xaaaaaaaa))>>1)

アルゴリズム分析:

#include <stdio.h>

#define EXCHANGEBIN(NUM) (((NUM)&(0x55555555))<<1)|(((NUM)&(0xaaaaaaaa))>>1)


//宏测试

int main()
{
    int num = 426;                      //二进制补码为:0000 0000 0000 0000 0000 0001 1010 1010
    printf("%u\n",EXCHANGEBIN(num));    //转换后补码为:0000 0000 0000 0000 0000 0010 0101 0101
    return 0;
}

 

5. #define 使用上の注意事項まとめ

  • 副作用(変数の値を変更する)を伴う式をマクロの実パラメータとして使用する場合、マクロ本体での出現回数の影響に注意してください。
  • マクロ本体の定義では、操作の結合性を明確に示すために、より多くの括弧を使用する必要があります
  • マクロ パラメータは、任意の型の変数または型名にすることができます。使用するときは、適切な型の一致に注意してください
  • マクロ テキスト置換メカニズムは、コードの保守性を低下させます(ソース テキストで置換されたマクロは、ソース コードのコンテキストとの予期しない相互作用を持ちます)。より複雑なプロセスは、マクロによってカプセル化されるべきではありません
  • マクロ本体はコンパイル前の段階でソース コードのテキストに置き換えられますが、コードをデバッグすると、実際にデバッグされたコード セグメントとは異なるソース コード セグメントがユーザーに表示されます。
  • プリプロセッサが #define で定義されたシンボルを検索するとき、文字列定数の内容は検索されません。

    例えば:

    #define N 4
    char arr[]="N";
    //arr中的N不会被替换

6. マクロと機能の比較


#define EXCHANGEBIN(NUM) (((NUM)&(0x55555555))<<1)|(((NUM)&(0xaaaaaaaa))>>1)

int ExchangeBin(int num)
{
    return (((num)&(0x55555555))<<1)|(((num)&(0xaaaaaaaa))>>1);
}

int main()
{
    int num = 426;                  
    EXCHANGEBIN(num);  
    ExchangeBin(num);

    return 0;
}

コード セグメント内のマクロと関数は同じ機能を実現しますが、実際にはマクロを実行するためのアセンブリ命令と関数を実行するためのアセンブリ命令はまったく異なります。

EXCHANGEBIN(num) マクロのアセンブリ命令を実行します。

ExchangeBin(num) 関数を実行するためのアセンブリ手順:

  • 同じ機能を達成するために、マクロを実行するための命令セグメントは、関数を実行するための命令セグメントよりもはるかに簡潔であることがわかります. したがって、プログラムで頻繁に使用されるいくつかの単純な式については、マクロを使用してそれらをカプセル化するプログラムはより効率的実行されます。
  • マクロはテキスト置換であるため、前処理後、マクロによってソース コードのテキスト長が大幅に増加し、実行時にプログラムがより多くのメモリを消費する可能性がありますが、関数関数本体はメモリの読み取り専用の定数領域
  • マクロ パラメーターには型の制限がありません (型名も)。一方、関数パラメーターには厳密な型チェックがあり、この時点では関数の方が安全です。
  • マクロ本体はステートメントごとにデバッグするのは困難ですが (展開されたマクロ本体はソース ファイルのマクロ呼び出しステートメントに表示されません)、関数はステートメントごとにデバッグできます。
  • マクロは再帰できません、関数は再帰できます

C++ のマクロ用に提案された新しい構文機能:

マクロの長所と短所を要約した後、新しいコンパイル時の機能がC++ 構文で与えられます: inline function

C++ でマクロの代わりにインライン関数を使用するプログラムの効率を向上させながら、コードのセキュリティと保守性を向上させることができます (マクロの最大の欠点は、型の安全性チェックがないことと、マクロ本体がコンテキスト コード セグメントの影響を受けることです。 . 可読性と保守性が低い)。C++ インライン関数については、http: //t.csdn.cn/cWQPLを参照してください。

7.#undef

#undef ディレクティブは、マクロ定義を削除するために使用されます

#undef NAME

添付: #define に関する 3 つの冷たい事実

  1. # を使用して、マクロ パラメータを対応する文字列に変換します
    int i = 10;
    #define PRINT(FORMAT, VALUE)\            //宏体可以分行定义(用反斜杠加回车将宏体内容换行)
    printf("the value of " #VALUE "is "FORMAT "\n", VALUE);
    
    
    int main()
    {
       PRINT("%d", i+3);  // #VALUE会使参数 i+3 变为对应的字符串"i+3"
    }
    
  2. ## 両側の記号を 1 つの記号に結合できます。
    これにより、マクロ定義で個別のテキスト フラグメントから識別子を作成できます (識別子は事前に定義する必要があります)。
    #define ADD_TO_SUM(num, value) \
    sum##num += value;
    
    
    int main ()
    {
        int sum5 =10;
        ADD_TO_SUM(5, 10); //预处理将该语句替换为 sum5 += 10
    }
    
  3. gcc のコマンドライン定義:
    #include <stdio.h>
    int main()
    {
        int array [ARRAY_SIZE];
        return 0;
    }

    gcc コンパイル コマンド ラインで定数 ARRAY_SIZE の値を指定できます。

    gcc -D ARRAY_SIZE=10 -E ./testproject/test.c -o test.i

3. 条件付きコンパイル

  1. 定数式の条件付きコンパイル:

    //单分支条件编译指令
    #if 常量表达式
    
        //代码段
    
    #endif
    
    //多分支条件编译指令
    #if   常量表达式
       
     //代码段
    
    #elif 常量表达式
        
     //代码段
    
    #else
    
     //代码段
    
    #endif
    
    //#elif 可以类比 else if 来理解

    定数式が true の場合、#if と #endif (または #elif、#else) の間のコード セグメントがコンパイルされ、false の場合、コンパイラは自動的に#if と #endif (または #elif、#else)をブロックします。 else ) コード セグメント間(定数式はプリプロセッサによって評価されます)

  2. #define の条件付きコンパイル

    ​
    
    #ifdef symbol
    
       //代码段     
        
    #enif
    
    如果symbol被#define定义了,则编译器会编译代码段,如果symbol没有被#define定义,则编译器不会编译代码段
    
    ​
    #ifndef symbol
    
        //代码段    
    
    #endif
    
    如果symbol没有被#define定义则编译器会编译代码段,如果symbol被#define定义了则编译器不会编译代码段
条件编译的嵌套
#ifdef OS_Unix

    #ifdef OPTION1
        unix_version_option1();
    #endif

    #ifdef OPTION2
        unix_version_option2();
    #endif

#elifdef OS_MSDOS

    #ifdef OPTION2
        msdos_version_option2();
    #endif

#endif

 条件付きコンパイルは、言語標準ライブラリ ソースだけでなく、一部のプロジェクトでも一般的です。

4. 前処理 #include

#include の機能は、指定されたヘッダー ファイルの内容を現在のソース ファイル「コピー アンド ペースト」することです。

1.#include<>と#include""

  • ローカル ファイルにはディレクティブが含まれています
    #include "filename"

    コンパイラ検索方法: コンパイラはまず、現在のソース ファイルが配置されているパスでファイル名ファイルを検索します. ヘッダー ファイルが見つからない場合、コンパイラは標準ライブラリ パスファイル名ファイルを検索します. 見つからない場合はコンパイル エラー

  • ライブラリファイルにはディレクティブが含まれています
     

    #include <filename>

    コンパイラは、標準ライブラリ パスに直接アクセスしてファイル名 fileを見つけます。見つからない場合は、コンパイル エラーが表示されます。

特定の状況に応じて対応するファイル インクルード命令を選択すると、コンパイラのコンパイル効率が向上し、ソース コード レベルでライブラリ ファイルとローカル ファイルを区別しやすくなります。

2.ヘッダーファイルの重複インクルード

同じソース ファイルにヘッダー ファイルを重複して含める

このようなシナリオは、複雑なプロジェクト エンジニアリングに簡単に現れます。

上記のシナリオでは、 comm.h がtest.c に2 回繰り返し含まれています。これは、 comm.h の同じ内容がtest.c に 2 回「コピー アンド ペースト」され、プログラム リンク エラー (このリンク エラー) が発生する可能性があることを意味します。多くの場合、長い間人を投げます)

  • ヘッダー ファイルに #pragma once ディレクティブを追加すると、ヘッダー ファイルが同じソース ファイルに繰り返しインクルードされるのを防ぐことができます。
    #pragma once

    #pragma onceディレクティブを各ヘッダー ファイルに追加することをお勧めします。

複数のソースファイル同じヘッダーファイルが含まれています

  • ヘッダー ファイルは複数のソース ファイルによって同時にインクルードされることが多いため、グローバル変数の定義と関数本体の定義はヘッダー ファイルで避ける必要があります。そうしないと、同じ変数 (または関数) の定義が複数出現します。グローバル ドメインで 2 回発生し、リンク エラーが発生します。
  • グローバル識別名宣言と定義を分離し、宣言はヘッダーファイルに、定義はソースファイルに一様に配置するという、必要なプログラミング品質です。

おすすめ

転載: blog.csdn.net/weixin_73470348/article/details/128961818