質問

C を少し書きましたが、それが何をしているのかについての一般的なアイデアを得るのに十分に読むことができますが、マクロに遭遇するたびに完全にスローされます。最終的に、マクロとは何かを思い出し、読んでいるうちにそれを頭の中で置き換えなければなりません。直観的で理解しやすいものに出会ったことは、いつも小さなミニ機能のようなものだったので、なぜそれらが単なる機能ではないのかといつも疑問に思っていました。

プリプロセッサでデバッグまたはクロスプラットフォームビルドのさまざまなビルドタイプを定義する必要性は理解できますが、任意の置換を定義する機能は、すでに難しい言語をさらに理解しにくくするためにのみ役立つようです。

C にこのような複雑なプリプロセッサが導入されたのはなぜですか?そして、誰かがそれを#debugスタイルの条件付きコンパイルの単純な以外の目的のためにまだ使用されるように思われる理由を私に理解させるそれを使用する例がありますか?

編集:

いくつかの回答を読んでも、まだ理解できません。最も一般的な答えは、インラインコードです。インラインキーワードがそれを行わない場合、それを行わない正当な理由があるか、実装を修正する必要があります。 <!> quot;このコードを本当にインラインで記述する<!> quot;を意味するまったく異なるメカニズムが必要な理由がわかりません。 (インラインが存在する前に書かれているコードは別として)。また、<!> quot;関数に入れるにはあまりにも愚かな場合は、<!> quot;という言及された考えも理解していません。確かに、入力を受け取って出力を生成するコードは、関数に入れるのが最適です。 C を書くという微妙な最適化に慣れていないので、うまくいかないかもしれませんが、プリプロセッサはいくつかの簡単な問題に対する複雑な解決策のように感じます。

役に立ちましたか?

解決

  

最終的に、マクロとは何かを思い出し、読んでいるうちにそれを頭の中で置き換えなければなりません。

それはマクロの命名にあまり反映していないようです。プリプロセッサがlog_function_entry()マクロであれば、プリプロセッサをエミュレートする必要はないと思います。

  

直観的で理解しやすいものに出会ったのは、いつも小さなミニ機能のようなものだったので、なぜ機能だけではないのかといつも思っていました。

通常は、一般的なパラメーターを操作する必要がない限り、あるべきです。

#define max(a,b) ((a)<(b)?(b):(a))

は、<演算子を含むすべてのタイプで機能します。

マクロは機能だけでなく、ソースファイル内のシンボルを使用して操作を実行できます。つまり、新しい変数名を作成したり、マクロが存在するソースファイルと行番号を参照したりできます。

C99では、マクロを使用して、printfなどの可変長関数を呼び出すこともできます

#define log_message(guard,format,...) \
   if (guard) printf("%s:%d: " format "\n", __FILE__, __LINE__,__VA_ARGS_);

log_message( foo == 7, "x %d", x)

形式はprintfのように機能します。ガードがtrueの場合、メッセージを出力したファイルと行番号とともにメッセージを出力します。関数呼び出しの場合は、呼び出し元のファイルと行がわからないため、vaprintfを使用するともう少し手間がかかります。

他のヒント

この抜粋は、Cマクロが使用されるいくつかの方法と、Dでそれらを実装する方法を比較することで、この問題に関する私の見解をかなり要約しています。

DigitalMars.comからコピー

  

#includeが発明されたときに戻る、コンパイラ   テクノロジーは原始的でした。インストールする   前面にテキストマクロプリプロセッサ   終わりは簡単で簡単な方法でした   多くの強力な機能を追加します。の   サイズの増加<!> amp;の複雑さ   プログラムはこれらを示しています   機能には多くの固有の   問題。 C++にはありません   プリプロセッサ;しかし、<=>はより多くを提供します   同じことを解決するスケーラブルな手段   問題。

マクロ

プリプロセッサマクロは、<=>に強力な機能と柔軟性を追加します。しかし、彼らには欠点があります:

  • マクロにはスコープの概念がありません。定義点からソースの最後まで有効です。 .hファイル、ネストされたコードなどにスワスをカットします。数万行のマクロ定義を<=> 'する場合、意図しないマクロの展開を避けることが問題になります。
  • マクロはデバッガーに認識されていません。シンボリックデータを使用してプログラムをデバッグしようとすると、デバッガーはマクロ自体ではなく、マクロ展開のみを認識してしまいます。
  • マクロを使用すると、ソースコードをトークン化できなくなります。これは、以前のマクロの変更によりトークンを任意にやり直すことができるためです。
  • マクロの純粋なテキストベースは、arbitrary意的で一貫性のない使用法につながり、マクロを使用するコードにエラーが発生しやすくなります。 (これを解決するためのいくつかの試みは、<=>のテンプレートで導入されました。)
  • マクロは、<!> quot; wrappers <!> quotなど、言語の表現力の不足を補うために引き続き使用されます。ヘッダーファイルの周り。

マクロの一般的な使用法の列挙と、Dの対応する機能:

  1. リテラル定数の定義:

    • <=>プリプロセッサの方法

      #define VALUE 5
      
    • <=>方法

      const int VALUE = 5;
      
  2. 値またはフラグのリストの作成:

    • <=>プリプロセッサの方法

      int flags:
      #define FLAG_X  0x1
      #define FLAG_Y  0x2
      #define FLAG_Z  0x4
      ...
      flags |= FLAG_X;
      
    • <=>方法

      enum FLAGS { X = 0x1, Y = 0x2, Z = 0x4 };
      FLAGS flags;
      ...
      flags |= FLAGS.X;
      
  3. 関数呼び出し規約の設定:

    • <=>プリプロセッサの方法

      #ifndef _CRTAPI1
      #define _CRTAPI1 __cdecl
      #endif
      #ifndef _CRTAPI2
      #define _CRTAPI2 __cdecl
      #endif
      
      int _CRTAPI2 func();
      
    • <=>方法

      呼び出し規約はブロック単位で指定できるため、すべての関数で変更する必要はありません:

      extern (Windows)
      {
          int onefunc();
          int anotherfunc();
      }
      
  4. 単純な汎用プログラミング:

    • <=>プリプロセッサの方法

      テキスト置換に基づいて使用する関数を選択する:

      #ifdef UNICODE
      int getValueW(wchar_t *p);
      #define getValue getValueW
      #else
      int getValueA(char *p);
      #define getValue getValueA
      #endif
      
    • <=>方法

      <=>は、他のシンボルのエイリアスであるシンボルの宣言を有効にします。

      version (UNICODE)
      {
          int getValueW(wchar[] p);
          alias getValueW getValue;
      }
      else
      {
          int getValueA(char[] p);
          alias getValueA getValue;
      }
      

DigitalMars Webサイトには他の例があります。

これらはCの上にあるプログラミング言語(より単純なもの)であるため、コンパイル時にメタプログラミングを行うのに役立ちます。つまり、より少ない行と時間でCコードを生成するマクロコードを書くことができます。 Cで直接書き込む必要があります。

これらは<!> quot; <!> quotのような関数を書くのにも非常に便利です。 <!> quot; polymorphic <!> quot;である式または<!> quot; overloaded <!> quot ;;例えば次のように定義された最大マクロ:

#define max(a,b) ((a)>(b)?(a):(b))

は、あらゆる数値タイプに役立ちます。 Cでは次のように書くことができませんでした:

int max(int a, int b) {return a>b?a:b;}
float max(float a, float b) {return a>b?a:b;}
double max(double a, double b) {return a>b?a:b;}
...

関数をオーバーロードできないため、必要に応じて。

そして、条件付きコンパイルとファイルインクルードは言うまでもありません(マクロ言語の一部でもあります)...

マクロを使用すると、コンパイル時にプログラムの動作を変更できます。これを考慮してください:

  • C定数により、開発時のプログラムの動作を修正できます
  • C変数を使用すると、実行時にプログラムの動作を変更できます
  • Cマクロを使用すると、コンパイル時にプログラムの動作を変更できます

コンパイル時とは、マクロプリプロセッサに統合されている限り、未使用のコードがバイナリに入らず、ビルドプロセスが値を変更できることを意味します。例:make ARCH = arm(転送マクロ定義をcc -DARCH = armと想定)

簡単な例: (glibc limits.hから、longの最大値を定義します)

#if __WORDSIZE == 64
#define LONG_MAX 9223372036854775807L
#else
#define LONG_MAX 2147483647L
#endif

32ビットまたは64ビット用にコンパイルする場合、コンパイル時に(#define __WORDSIZEを使用して)検証します。 multilibツールチェーンでは、パラメータ-m32および-m64を使用すると、ビットサイズが自動的に変更される場合があります。

(POSIXバージョンリクエスト)

#define _POSIX_C_SOURCE 200809L

コンパイル時のリクエストPOSIX 2008サポート。標準ライブラリは多くの(互換性のない)標準をサポートしますが、この定義により、正しい関数プロトタイプ(例:getline()、gets()など)を提供します。ライブラリが標準をサポートしていない場合、たとえば、実行中にクラッシュする代わりに、コンパイル時に#errorが発生する場合があります。

(ハードコードされたパス)

#ifndef LIBRARY_PATH
#define LIBRARY_PATH "/usr/lib"
#endif

コンパイル時にハードコードディレクトリを定義します。たとえば、-DLIBRARY_PATH = / home / user / libで変更できます。それがconst char *の場合、コンパイル中にどのように設定しますか?

(pthread.h、コンパイル時の複雑な定義)

# define PTHREAD_MUTEX_INITIALIZER \
  { { 0, 0, 0, 0, 0, 0, { 0, 0 } } }

それ以外の場合は単純化されない大きなテキストを宣言することができます(常にコンパイル時に)。関数または定数を使用してこれを行うことはできません(コンパイル時)。

本当に複雑なことを避け、コーディングスタイルの質を落とすことを避けるために、異なる、互換性のないオペレーティングシステムでコンパイルするコードの例を挙げません。そのためにクロスビルドシステムを使用しますが、プリプロセッサがビルドシステムの助けなしで、インターフェイスがないためにコンパイルを中断することなくそれを許可することは明らかです。

最後に、プロセッサ速度とメモリが制限され、システムが非常に異種である組み込みシステムでの条件付きコンパイルの重要性について考えます。

今、あなたが尋ねると、すべてのマクロ定数定義と関数呼び出しを適切な定義に置き換えることは可能ですか?答えはイエスですが、コンパイル中にプログラムの動作を変更する必要がなくなるだけではありません。プリプロセッサは引き続き必要です。

マクロ(およびプリプロセッサ)はCの初期の時代のものであることに注意してください。これらは、インラインの「関数」を実行する唯一の方法でした(もちろん、インラインはごく最近のキーワードであるため)インライン化するものを強制する唯一の方法です。

また、マクロは、コンパイル時にファイルと行を文字列定数に挿入するなどのトリックを実行できる唯一の方法です。

最近では、マクロが唯一の方法であったことの多くは、新しいメカニズムによって適切に処理されます。しかし、彼らは時々自分の場所を持っています。

効率と条件付きコンパイルのためのインライン化とは別に、マクロを使用して低レベルCコードの抽象化レベルを上げることができます。 Cは、メモリとリソースの管理とデータの正確なレイアウトの本質的な詳細からあなたを実際に隔離せず、非常に限られた形式の情報隠蔽と大規模システムを管理するための他のメカニズムをサポートします。マクロを使用すると、C言語のベースコンストラクトのみの使用に制限されなくなります。名目上Cを記述しながら、独自のデータ構造とコーディングコンストラクト(クラスとテンプレートを含む!)を定義できます。

プリプロセッサマクロは、実際にコンパイル時に実行されるチューリング完了言語を提供します。これの印象的な(そして少し怖い)例の1つは、C ++側で終わりました: Boost Preprocessor ライブラリは、 C99 /を使用します。 C ++ 98 (比較的)安全なプログラミング構造を構築するプリプロセッサ。その後、基礎となる宣言に展開されます。また、CまたはC ++のいずれであっても、入力したコード。

実際には、より安全な言語で高レベルの構造を使用する自由がない場合、プリプロセッサプログラミングを最後の手段と見なすことをお勧めします。しかし、時には、背中が壁に接していて、イタチが近づいている場合に何ができるかを知っておくとよいでしょう...!

コンピューターの愚かさから:

  

UNIX用の多くのフリーウェアゲームプログラムでこのコードの抜粋を見ました:

     

/ *
   *ビット値。
   * /
   #define BIT_0 1
   #define BIT_1 2
   #define BIT_2 4
   #define BIT_3 8
   #define BIT_4 16
   #define BIT_5 32
   #define BIT_6 64
   #define BIT_7 128
   #define BIT_8 256
   #define BIT_9 512
   #define BIT_10 1024
   #define BIT_11 2048
   #define BIT_12 4096
   #define BIT_13 8192
   #define BIT_14 16384
   #define BIT_15 32768
   #define BIT_16 65536
   #define BIT_17 131072
   #define BIT_18 262144
   #define BIT_19 524288
   #define BIT_20 1048576
   #define BIT_21 2097152
   #define BIT_22 4194304
   #define BIT_23 8388608
   #define BIT_24 16777216
   #define BIT_25 33554432
   #define BIT_26 67108864
   #define BIT_27 134217728
   #define BIT_28 268435456
   #define BIT_29 536870912
   #define BIT_30 1073741824
   #define BIT_31 2147483648

     

これを実現するはるかに簡単な方法は次のとおりです。

     

#define BIT_0 0x00000001
   #define BIT_1 0x00000002
   #define BIT_2 0x00000004
   #define BIT_3 0x00000008
   #define BIT_4 0x00000010
   ...
   #define BIT_28 0x10000000
   #define BIT_29 0x20000000
   #define BIT_30 0x40000000
   #define BIT_31 0x80000000

     

さらに簡単な方法は、コンパイラーに計算を行わせることです:

     

#define BIT_0(1)
   #define BIT_1(1 <!> lt; <!> lt; 1)
   #define BIT_2(1 <!> lt; <!> lt; 2)
   #define BIT_3(1 <!> lt; <!> lt; 3)
   #define BIT_4(1 <!> lt; <!> lt; 4)
   ...
   #define BIT_28(1 <!> lt; <!> lt; 28)
   #define BIT_29(1 <!> lt; <!> lt; 29)
   #define BIT_30(1 <!> lt; <!> lt; 30)
   #define BIT_31(1 <!> lt; <!> lt; 31)

     

しかし、なぜ32個の定数を定義するのに苦労するのですか? C言語には、パラメーター化されたマクロもあります。本当に必要なのは:

     

#define BIT(x)(1 <!> lt; <!> lt;(x))

     

とにかく、元のコードを書いた人は電卓を使ったのか、それとも紙で計算しただけなのかしら。

これは、マクロの使用法の1つにすぎません。

マクロが本当に輝くケースの1つは、マクロを使用してコード生成を行うときです。

以前は、独自の方法でプラグインにパラメーターを渡すプラグインシステムを使用していた古いC ++システムで作業していました(カスタムマップのような構造を使用)。いくつかの簡単なマクロを使用してこの癖に対処し、プラグインで通常のパラメーターを使用して実際のC ++クラスと関数を問題なく使用できるようにしました。マクロによって生成されるすべてのグルーコード。

すでに述べたことに追加します。

マクロはテキスト置換で機能するため、関数を使用しては不可能な非常に便利なことを実行できます。

ここで、マクロが本当に役立ついくつかのケースを示します。

/* Get the number of elements in array 'A'. */
#define ARRAY_LENGTH(A) (sizeof(A) / sizeof(A[0]))

これは非常に人気があり、頻繁に使用されるマクロです。これは、たとえば配列を反復処理する必要がある場合に非常に便利です。

int main(void)
{
    int a[] = {1, 2, 3, 4, 5};
    int i;
    for (i = 0; i < ARRAY_LENGTH(a); ++i) {
        printf("a[%d] = %d\n", i, a[i]);
    }
    return 0;
}

ここで、別のプログラマーが宣言のaにさらに5つの要素を追加してもかまいません。 for-loopは、すべての要素を常に反復します。

メモリと文字列を比較するCライブラリの関数は、使用するのが非常に面倒です。

書きます:

char *str = "Hello, world!";

if (strcmp(str, "Hello, world!") == 0) {
    /* ... */
}

または

char *str = "Hello, world!";

if (!strcmp(str, "Hello, world!")) {
    /* ... */
}

str"Hello, world"を指しているかどうかを確認します。個人的には、これらのソリューションはどちらも非常にくて混乱していると思います(特に!strcmp(...))。

strcmp / memcmpを使用して文字列またはメモリを比較する必要がある場合に使用する2つのきちんとしたマクロ(Iを含む)があります:

/* Compare strings */
#define STRCMP(A, o, B) (strcmp((A), (B)) o 0)

/* Compare memory */
#define MEMCMP(A, o, B) (memcmp((A), (B)) o 0)

これで、次のようなコードを記述できるようになりました。

char *str = "Hello, world!";

if (STRCMP(str, ==, "Hello, world!")) {
    /* ... */
}

ここに意図がより明確になっています!

これらは、関数が達成できないことのためにマクロが使用される場合です。マクロは関数を置き換えるために使用すべきではありませんが、他の優れた用途があります。

質問内のコメントを考えると、関数を呼び出すとかなりのオーバーヘッドが発生する可能性があることを十分に理解できないかもしれません。パラメータとキーレジスタは、途中でスタックにコピーし、途中でスタックを解く必要があります。これは特に古いIntelチップに当てはまりました。マクロを使用すると、プログラマは関数の抽象化を(ほぼ)維持できますが、関数呼び出しのコストのかかるオーバーヘッドを回避できます。 inlineキーワードは助言的ですが、コンパイラーが常に正しいとは限りません。 「C」の栄光と危険は、通常、コンパイラを意のままに曲げることができることです。

パンとバターでは、この種のマイクロ最適化(関数呼び出しの回避)をプログラミングする日常的なアプリケーションは、一般に役に立たないよりも悪いですが、オペレーティングのカーネルによって呼び出されるタイムクリティカルな関数を書いている場合システム、それは大きな違いを生むことができます。

通常の関数とは異なり、マクロで制御フロー(if、while、for、...)を実行できます。次に例を示します。

#include <stdio.h>

#define Loop(i,x) for(i=0; i<x; i++)

int main(int argc, char *argv[])
{
    int i;
    int x = 5;
    Loop(i, x)
    {
        printf("%d", i); // Output: 01234
    } 
    return 0;
} 

コードをインライン化し、関数呼び出しのオーバーヘッドを回避するのに適しています。同様に、多くの場所を編集せずに後で動作を変更する場合に使用します。複雑なものには役立ちませんが、インラインにしたいコードの単純な行には、悪くありません。

Cプリプロセッサのテキスト操作を活用することにより、多態的なデータ構造に相当するCを構築できます。この手法を使用すると、特定の実装の仕様ではなくC構文を利用するため、任意のCプログラムで使用できるプリミティブデータ構造の信頼できるツールボックスを構築できます。

データ構造を管理するためにマクロを使用する方法の詳細な説明はここにあります- http://multi-core-dump.blogspot.com/2010/11/interesting-use-of-c-macros-polymorphic.html

マクロを使用すると、コピーアンドペーストされたフラグメントを取り除くことができます。これは他の方法で削除することはできません。

たとえば(VS 2010コンパイラの実際のコードの構文):

for each (auto entry in entries)
{
        sciter::value item;
        item.set_item("DisplayName",    entry.DisplayName);
        item.set_item("IsFolder",       entry.IsFolder);
        item.set_item("IconPath",       entry.IconPath);
        item.set_item("FilePath",       entry.FilePath);
        item.set_item("LocalName",      entry.LocalName);
        items.append(item);
    }

これは、同じ名前のフィールド値をスクリプトエンジンに渡す場所です。これはコピーペーストですか?はい。 DisplayNameは、スクリプトの文字列およびコンパイラのフィールド名として使用されます。それは悪いですか?はい。コードをリファクタリングしてLocalNameRelativeFolderNameに名前変更し(私がしたように)、文字列で同じことを忘れた場合(私がしたように)、スクリプトは予期しない方法で動作します(実際、私の例では、別のスクリプトファイルでフィールドの名前を変更するのを忘れたかどうかに依存しますが、スクリプトがシリアル化に使用される場合、100%のバグになります。

これにマクロを使用する場合、バグの余地はありません。

for each (auto entry in entries)
{
#define STR_VALUE(arg) #arg
#define SET_ITEM(field) item.set_item(STR_VALUE(field), entry.field)
        sciter::value item;
        SET_ITEM(DisplayName);
        SET_ITEM(IsFolder);
        SET_ITEM(IconPath);
        SET_ITEM(FilePath);
        SET_ITEM(LocalName);
#undef SET_ITEM
#undef STR_VALUE
        items.append(item);
    }

残念ながら、これは他のタイプのバグへの扉を開きます。コンパイラはすべての前処理の後にどのように見えるかを示さないので、マクロを記述するタイプミスを行うことができ、甘やかされたコードを見ることはありません。他の誰かが同じ名前を使用することもできます(だからこそ<!> quot; release <!> quot; #undefでマクロをできるだけ早く)。だから、賢くそれを使用してください。コピーペーストされたコード(関数など)を取り除く別の方法を見つけた場合は、その方法を使用してください。コピーペーストされたコードをマクロで削除しても効果がない場合は、コピーペーストされたコードを保持してください。

明らかな理由の1つは、マクロを使用することで、コンパイル時にコードが展開され、呼び出しのオーバーヘッドなしで擬似関数呼び出しが得られることです。

それ以外の場合は、シンボリック定数にも使用できます。そのため、1つの小さなものを変更するために複数の場所で同じ値を編集する必要はありません。

マクロ.. <!> amp;#(* $ <!> amp;コンパイラーが何かをインライン化することを拒否する場合。

それはやる気を起こさせるポスターであるべきですか?

真剣に、Google プリプロセッサの乱用(#1と同様のSO質問が表示される場合があります結果)。 assert()の機能を超えるマクロを書いている場合、通常、コンパイラが実際に同様の関数をインライン化するかどうかを確認しようとします。

条件付きコンパイルに#ifを使用することに反対する人もいます。

if (RUNNING_ON_VALGRIND)

ではなく

#if RUNNING_ON_VALGRIND

.. if()は表示できますが、デバッガでは#ifは表示されないため、デバッグ目的で使用します。次に、#ifdefと#ifについて説明します。

コードが10行未満の場合は、インライン化してみてください。インライン化できない場合は、最適化してみてください。あまりにも愚かすぎて関数にならない場合は、マクロを作成します。

私はマクロの大ファンではなく、現在のタスクに基づいてCをあまり書く傾向はありませんが、次のようなもの(明らかに副作用がある可能性があります)は便利です:

#define MIN(X, Y)  ((X) < (Y) ? (X) : (Y))

今、私は何年もそのようなものを書いていませんが、そのような「機能」は、私のキャリアの初期に維持していたコード全体にありました。拡張は便利だと考えられると思います。

マクロなどの機能に関して、これについて言及している人は誰もいませんでした。例:

#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))

一般的に、必要でないときはマクロの使用を避けることをお勧めします。多くの理由で、読みやすさが主な関心事です。だから:

  

関数でこれらを使用する必要がある場合

ほとんどの場合、inlineのより読みやすい代替手段があるため、 https://www.greenend.org.uk/rjk/tech/inline.html または http://www.cplusplus.com/articles/2LywvCM9/ (2番目のリンクはC ++ページですが、ポイントは私の知る限りcコンパイラに適用可能です。

現在、わずかな違いは、マクロはプリプロセッサによって処理され、インラインはコンパイラによって処理されることですが、現在では実際的な違いはありません。

  

これらを使用するのはいつが適切ですか?

小さな機能の場合(最大2つまたは3つのライナー)。マクロ(およびインライン関数)のような関数は、前処理(またはインラインの場合はコンパイル)中に行われるコード置換であり、メモリに存在する実際の関数ではないため、プログラムの実行中にいくつかの利点を得ることが目標です。そのため、関数呼び出しのオーバーヘッドはありません(詳細はリンク先のページに記載されています)。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top