78

私は C を少し書いたことがあり、C が何をしているのかを理解するのに十分なほど十分に読むことができます。マクロが何であるかを覚えておき、読みながら頭の中で代用する必要があります。私が出会った直感的で分かりやすいものは、いつも小さな小さな機能のようなものでした.

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

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

編集:

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

4

18 に答える 18

56

マクロが何であるかを覚えておき、読みながら頭の中で代用する必要があります。

これは、マクロの命名にあまり反映されていないようです。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ます。ガードが真の場合、メッセージを出力したファイルと行番号とともにメッセージを出力します。関数呼び出しの場合、呼び出し元のファイルと行がわからないため、 a を使用するvaprintfと少し手間がかかります。

于 2009-03-17T11:46:50.130 に答える
19

この抜粋は、Cマクロが使用されるいくつかの方法と、D.

DigitalMars.com からのコピー

が発明されたC当時、コンパイラ技術は原始的でした。テキスト マクロ プリプロセッサをフロント エンドにインストールすることは、多くの強力な機能を追加するための単純明快な方法でした。プログラムのサイズと複雑さの増大は、これらの機能が多くの固有の問題を伴うことを示しています。Dプリプロセッサがありません。しかしD、同じ問題を解決するためのよりスケーラブルな手段を提供します。

マクロ

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

  • マクロにはスコープの概念がありません。それらは、定義の時点からソースの最後まで有効です。それらは、.h ファイル、ネストされたコードなどにまたがって一帯を切り取りました。#include何万行ものマクロ定義を 'ing する場合、不注意なマクロ展開を避けることが問題になります。
  • デバッガーはマクロを認識しません。シンボリック データを使用してプログラムをデバッグしようとする試みは、デバッガーがマクロ自体ではなく、マクロの展開についてしか認識していないために損なわれます。
  • マクロを使用すると、ソース コードをトークン化することができなくなります。これは、マクロを以前に変更すると、任意にトークンをやり直すことができるためです。
  • 純粋にテキストベースのマクロは恣意的で一貫性のない使用につながり、マクロを使用するコードはエラーが発生しやすくなります。(これを解決するためのいくつかの試みは、 のテンプレートで導入されましたC++。)
  • マクロは、ヘッダー ファイルの「ラッパー」など、言語の表現力の不足を補うために今でも使用されています。

マクロの一般的な使用法と、対応する D の機能の列挙を次に示します。

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

    • Cプリプロセッサの方法

      #define VALUE 5
      
    • D道_

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

    • Cプリプロセッサの方法

      int flags:
      #define FLAG_X  0x1
      #define FLAG_Y  0x2
      #define FLAG_Z  0x4
      ...
      flags |= FLAG_X;
      
    • D道_

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

    • Cプリプロセッサの方法

      #ifndef _CRTAPI1
      #define _CRTAPI1 __cdecl
      #endif
      #ifndef _CRTAPI2
      #define _CRTAPI2 __cdecl
      #endif
      
      int _CRTAPI2 func();
      
    • D道_

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

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

    • Cプリプロセッサの方法

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

      #ifdef UNICODE
      int getValueW(wchar_t *p);
      #define getValue getValueW
      #else
      int getValueA(char *p);
      #define getValue getValueA
      #endif
      
    • D道_

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

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

DigitalMarsの Web サイトには、さらに多くの例があります。

于 2009-03-18T00:26:56.130 に答える
16

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

また、「多態的」または「オーバーロード」された「関数のような」式を書くのにも非常に役立ちます。たとえば、次のように定義された max マクロ:

#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;}
...

関数をオーバーロードできないためです。

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

于 2009-05-14T11:03:56.480 に答える
11

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

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

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

于 2009-03-17T11:39:07.637 に答える
8

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

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

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

于 2009-03-17T12:22:43.243 に答える
5

マクロが真価を発揮するケースの 1 つは、マクロを使用してコードを生成する場合です。

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

于 2009-03-17T11:46:50.693 に答える
4

通常の関数とは異なり、マクロでは制御フロー (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;
} 
于 2013-06-20T10:23:20.277 に答える
3

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

于 2009-03-17T11:35:27.897 に答える
2

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

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

于 2011-11-05T18:57:28.697 に答える
1

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

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

于 2009-03-17T11:36:41.810 に答える
0

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

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

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

于 2009-03-17T11:42:05.977 に答える
0

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

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

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

関数に対してこれらをいつ使用する必要がありますか?

というより読みやすい代替inline手段があるため、ほとんどあり ませ。リンクは C++ のページですが、その点は私の知る限り c コンパイラに適用できます)。

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

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

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

于 2017-12-22T16:54:42.660 に答える
0

マクロ .. &#(*$& コンパイラが何かをインライン化することを拒否した場合。

それは動機付けのポスターになるはずですよね?

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

他の人は、条件付きコンパイルに #if を使用することに反対するでしょう..彼らはむしろあなたを望んでいます:

if (RUNNING_ON_VALGRIND)

それよりも

#if RUNNING_ON_VALGRIND

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

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

于 2009-03-17T11:33:12.177 に答える