web-dev-qa-db-ja.com

Cでマクロを使用する理由

可能な重複:
Cマクロは何のために便利ですか?

数か月ごとに、私のちょっとした大学のプログラミング教育ではカバーできなかったCを少し学ぶようになります。今日はマクロです。私のマクロの基本的な理解は、それらがあなたのコードで起こる単純な検索と置換であるということですpriorコンパイルされることです。 whyマクロを使用すると理解できない。私が見ている基本的な例のほとんどは次のようなものです

TEST(a,%d);
#define TEST(a,b) printf(" The value of " #a " = " #b " \n", a)

//which expands to 
printf(" The value of a = %d \n",a);

here の例)

私の初心者の観点からは、新しい関数を定義すると同じ結果が得られるようです。簡単に検索して置換する前に、マクロが多くのソースをすばやく修正するのに歴史的にどのように役立つかを見ることができますが、何か大きなポイントが不足していることがわかります。

それでは、マクロはあなたにとってどんな便利なことをすることができますか?

48
Alan Storm

1つの理由は、インラインキーワードがC言語の標準ではなかったC99までです。したがって、マクロを使用すると、小さな関数をインライン化できます。また、いくつかの点でテンプレートのように機能します。マクロ定義でタイプを指定する必要はありません。例:

#define MAX(x,y) ((x) > (y) ? (x) : (y))

このマクロは、整数、倍精度浮動小数点数などに準拠しています。

44
DeusAduro

正確に検索して置換するのではなく、トークンを展開します。 Cマクロは、コンピューティングの世界では他のすべての種類のマクロです。短くてシンプルなものを記述し、自動的に長くて複雑なものに変える方法です。

マクロが使用される理由の1つはパフォーマンスです。これらは、コンパイラにとってしばしば無視されるhintであり、存在さえしなかった「インライン」キーワードとは異なり、常にインラインで展開されるため、関数呼び出しのオーバーヘッドを排除する方法です。標準)C99より前。たとえば、selectおよびpselectで使用されるfd_setsと組み合わせて使用​​されるFD_ファミリのマクロを参照してください。これらのfd_setsは実際には単なるビットセットであり、FD_マクロはビット調整操作を隠しています。毎回自分自身をいじるビットを書き出すのは面倒で、関数呼び出しはインライン化されていない場合、このような高速な操作のために多くのオーバーヘッドになります。

また、マクロは機能ではできないことを実行できます。トークンの貼り付けを検討してください。プリプロセッサはコンパイラの前に実行されるため、コンパイラが使用する新しい識別子を作成できます。これにより、多くの同様の定義を簡単に作成できます。

#define DEF_PAIR_OF(dtype) \
  typedef struct pair_of_##dtype { \
       dtype first; \
       dtype second; \
  } pair_of_##dtype##_t 

 DEF_PAIR_OF(int);
 DEF_PAIR_OF(double);
 DEF_PAIR_OF(MyStruct);
 /* etc */

関数が実行できなかったもう1つのことは、コンパイル時の情報をランタイム情報に変換することです。

#ifdef DEBUG
#define REPORT_PTR_VALUE(v) printf("Pointer %s points to %p\n", #v, v)
#else 
#define REPORT_PTR_VALUE(v)
#endif

void someFunction(const int* reallyCoolPointer) {
  REPORT_PTR_VALUE(reallyCoolPointer);
  /* Other code */
}

マクロのように、関数が出力でパラメーターの名前を使用する方法はありません。これは、リリースビルド用のデバッグコードのコンパイルも示しています。

64
Tyler McHenry

マクロはコンパイル時に展開されます。よく悪用されることは間違いありませんが、Cの典型的な例は、デバッグメッセージを書き込むためのマクロを作成することです(コンパイル時にデバッグモードがオフになっている場合)。

15
Mike McQuaid

デバッグモードでのみロギングを行いたい場合があります。したがって、次のように記述します。

#ifdef DEBUG
#define LOG_MSG(x) printf(x);
#else
#define LOG_MSG(X)
#endif

コンパイル時のスイッチに基づいて何かをオンまたはオフにしたいだけの場合もあります。たとえば、私の会社は製品の共同ブランド化を行っており、パートナーは物事をオフにするよう求めています。そのため、次のようなことを行います。

#ifndef SPECIAL_PARTNER_BUILD
    DoSomethingReallyAwesome();
#endif

次に、パートナーにドロップを与えるときに-DSPECIAL_PARTNER_BUILDを使用してビルドします。

他の多くの考えられる理由の中で。

時々、繰り返し行うことのために入力を保存したいだけです。一部の人々はこれを遠いところに(cough MFC cough)、マクロで自分の言語に相当するものを書き、難しいAPIを抽象化します。これにより、デバッグが非常に困難になります。

12
i_am_jorf

マクロには、機能のようなもの以外にもさまざまな用途があります。

マジックナンバーや文字列に関連するものにマクロを使用すると非常に便利です。

#define MY_MAGIC_NUM 57

/*MY_MAGIC_NUM used all through out the code*/
5
lillq

Cマクロはコンパイル時にコードを生成できます。これは(ab)新しいキーワードと動作を使用してドメイン固有の言語を効果的に作成するために使用できます。

私のお気に入りの例の1つは、Cでマクロを使用する コルーチンの実装 のSimon Tathamの方法です。実装される最も単純なマクロは次のとおりです。

#define crBegin static int state=0; switch(state) { case 0:

はい、比類のないブレース付き。他のマクロがそれを修正します。

4
Dour High Arch

コードではこのタイプのマクロを使用しました。

// convenience macros for implementing field setter/getter functions
#ifndef CREATE_GET_SET
#define CREATE_GET_SET(PREFIX, FIELD_NAME,FIELD_DATATYPE) \
  protected:\
    FIELD_DATATYPE PREFIX ## FIELD_NAME;\
  public:\
  inline FIELD_DATATYPE get ## _ ## FIELD_NAME(void) const\
  { \
    return(PREFIX ## FIELD_NAME); \
  } \
  inline void set ## _ ## FIELD_NAME(FIELD_DATATYPE p) \
  { \
    PREFIX ## FIELD_NAME = p; \
  }
#endif

クラス/構造内で変数を定義します:

CREATE_GET_SET(_, id, unsigned int);

これにより、変数が定義され、コードの汎用ゲッター/セッターが作成されます。 get/setのための、よりクリーンで一貫したコード生成を実現します。もちろん、すべて記述できますが、大量の定型コードです注:これは、いくつかのマクロの1つにすぎません。私はそれらすべてを投稿しませんでした。このように "char *"を処理することはありません(データをstrncpyまたはstrcpyに設定する場合)。これは、マクロといくつかの単純なタイプでできることの単なるデモでした。

2
jmq

「設定ファイル」として使用すると最大の利点が得られると思います

#define USE_FAST_AGORHITM
//#define USE_SLOW_ONE

そしてグローバルな「定数」

#define MAX_NUMBER_OF_USERS 100000

ほとんどがマクロで記述されたプログラムがあります(たとえば、Brian GladmanのAES実装を確認してください http://www.gladman.me.uk/cryptography_technology/index.php

1
Luka Rahne

パフォーマンスが激しいシナリオでは、マクロを使用して「自動」ループ展開を作成できます。しかし、現代のコンパイラはおそらくこれでより良い仕事をしますが、それは以前は便利なアプリケーションでした。

1
korona

コンパイラはターゲットマシンに関する詳細をよく知っているため、条件付きコンパイルを使用して、ビッグエンディアンプロセッサ用のコードとリトルエンディアンプロセッサ用のコードを作成できます。これにより、異なるプロセッサ用に設計されたコードでコードが肥大化するのを防ぎます。

同様のケースは、特定のシステムにはアセンブリコードがあり、他のシステムにはCコードがある場合です。

1
Nosredna

マクロでは、関数のコードが呼び出し元のコードストリームに挿入されます。これにより、オプティマイザーは呼び出されたコードを手続き的に統合できるため、他の多くのことにもよりますが、パフォーマンスが向上します。呼び出されたコードを呼び出し元に最適化します。インライン関数(C++も使用している場合)と少しのマクロへの適切なリファレンスです。 http://www.parashift.com/c++-faq-lite/inline-functions.html#faq-9.5

0
Diego Dias