コンテンツにスキップ

最終更新:2026-10-12

C++ マクロと条件コンパイル

著者について:

個人公式アカウント(WeChat):

wechat

1.マクロに含まれる特殊な記号

いくつかの種類があります:#、##、\

1.1 文字列化演算子(#)

マクロ内の引数の前に1つの#を使うと、プリプロセッサはこの引数を文字配列に変換します。言い換えれば、**#は「文字列化」を意味し、マクロ定義の中の#は、その後ろに続く引数を文字列に変換する**ということです。

注意:引数を持つマクロ定義でしか使えず、マクロ定義本体の中の引数名の前に置かなければなりません。

例えば:

#define exp(s) printf("test s is:%s\n",s)
#define exp1(s) printf("test s is:%s\n",#s)
#define exp2(s) #s 
int main() {
    exp("hello");
    exp1(hello);

    string str = exp2(   bac );
    cout<<str<<" "<<str.size()<<endl;
    /**
     * 忽略传入参数名前面和后面的空格。
     */
    string str1 = exp2( asda  bac );
    /**
     * 当传入参数名间存在空格时,编译器将会自动连接各个子字符串,
     * 用每个子字符串之间以一个空格连接,忽略剩余空格。
     */
    cout<<str1<<" "<<str1.size()<<endl;
    return 0;
}

上述のコードは基本的な使用法と空白の処理ルールを示しています。空白の処理ルールは次の通りです:

  • 渡された引数名の前後の空白は無視される。
string str = exp2(   bac );
cout<<str<<" "<<str.size()<<endl;

出力:

bac 3
  • 渡された引数名の間に空白があるとき、コンパイラは自動的に各部分文字列を連結し、各部分文字列の間は1つの空白でつなぎ、余分な空白は無視される。
string str1 = exp2( asda  bac );
cout<<str1<<" "<<str1.size()<<endl;

出力:

asda bac 8

1.2 記号連結演算子(##)

「##」は一種の区切り連結方式であり、その役割はまず区切り、次に強制的に連結することです。マクロ定義の複数の仮引数を1つの実際の引数名に変換します。

注意事項:

(1)##で仮引数を連結するとき、##の前後の空白はあってもなくてもかまいません。

(2)連結後の実際の引数名は、実際に存在する引数名か、コンパイラが既に知っているマクロ定義でなければなりません。

(3)##の後の引数自体もマクロである場合、##はこのマクロの展開を阻止します。

例:

#define expA(s) printf("前缀加上后的字符串为:%s\n",gc_##s)  //gc_s必须存在
// 注意事项2
#define expB(s) printf("前缀加上后的字符串为:%s\n",gc_  ##  s)  //gc_s必须存在
// 注意事项1
#define gc_hello1 "I am gc_hello1"
int main() {
    // 注意事项1
    const char * gc_hello = "I am gc_hello";
    expA(hello);
    expB(hello1);
}

1.3 行継続演算子(\)

定義するマクロを1行で表しきれないときは、”\”を使って次の行でこのマクロの定義を続けることを表せます。

の前に空白を残すことに注意してください。

#define MAX(a,b) ((a)>(b) ? (a) \
   :(b))  
int main() {
    int max_val = MAX(3,6);
    cout<<max_val<<endl;
}

上述のコードはこちら:sig_examp.cpp

2.do{...}while(0)の使用

2.1 意味の誤解を避ける

例えば:

#define fun() f1();f2();
if(a>0)
    fun()

このマクロが展開されると次のようになります:

if(a>0)
    f1();
    f2();

意図としてはa>0のときf1とf2を実行することですが、実際にはf2が毎回実行されてしまうため、誤りになります。

この問題を解決するために、コードを書くときに、通常は{}ブロックを採用できます。

次の通り:

#define fun() {f1();f2();}
if(a>0)
    fun();
// 宏展开
if(a>0)
{
    f1();
    f2();
};

しかし、上述のマクロ展開後には余分なセミコロンが1つ付いており、実際の文法としては正しくありません。(コンパイルと実行には問題ありませんが、本来はセミコロンがありません。)

2.2 gotoによる制御フローの使用を避ける

一部の関数では、return文の前に何らかのクリーンアップ作業、例えば関数の先頭でmallocによって確保されたメモリ領域を解放する必要があることがあります。gotoを使うのは常に簡単な方法です:

int f() {
    int *p = (int *)malloc(sizeof(int));
    *p = 10; 
    cout<<*p<<endl;
#ifndef DEBUG
    int error=1;
#endif
    if(error)
        goto END;
    // dosomething
END:
    cout<<"free"<<endl;
    free(p);
    return 0;
}

しかし、gotoはソフトウェア工学の構造化に合致せず、コードを分かりにくくする可能性があるため、多くの人は使用を推奨していません。このようなとき、do{...}while(0)を使って同じことができます:

int ff() {
    int *p = (int *)malloc(sizeof(int));
    *p = 10; 
    cout<<*p<<endl;
    do{ 
#ifndef DEBUG
        int error=1;
#endif
        if(error)
            break;
        //dosomething
    }while(0);
    cout<<"free"<<endl;
    free(p);
    return 0;
}

ここでは関数の本体部分をdo{...}while(0)で囲み、breakを使ってgotoを置き換えています。後続のクリーンアップ作業はwhileの後ろに置かれます。これにより同じ効果が得られるだけでなく、コードの可読性や保守性も上のgotoのコードよりはるかに良くなります。

2.3 マクロによる警告を避ける

カーネルでは、異なるアーキテクチャの制約のため、空マクロが使われることが非常に多くあります。コンパイル時、これらの空マクロはwarningを出します。このようなwarningを避けるために、do{...}while(0)を使って空マクロを定義できます:

#define EMPTYMICRO do{}while(0)

2.4 複雑な操作を完了させる単一の関数ブロックを定義する

複雑な関数があり、変数が多く、新しい関数を増やしたくない場合は、do{...}while(0)を使って、その中にあなたのコードを書くことができます。中では変数を定義でき、変数名が関数の前や後ろのものと重なることを心配する必要はありません。 この状況は、1つの変数が複数箇所で使われる(しかし各箇所で意味が異なる)場合を指します。各do-whileの中でスコープを狭めることができます。例えば:

int fc()
{
    int k1 = 10;
    cout<<k1<<endl;
    do{
        int k1 = 100;
        cout<<k1<<endl;
    }while(0);
    cout<<k1<<endl;
}

上述のコードはこちら:do_while.cpp

学習記事:https://www.cnblogs.com/lizhenghn/p/3674430.html

関連トピック

コメント