[〜#〜] c [〜#〜]を少し書いたので、それが何をしているのかについての一般的なアイデアを得るのに十分に読むことができますが、出会うたびにマクロが完全に私を投げました。マクロが何であるかを覚えて、読んでいるうちに頭の中で置き換えなければなりません。直観的で理解しやすいものに出会ったのはいつも小さなミニ機能のようなものだったので、なぜそれらが単なる機能ではないのかといつも疑問に思いました。
プリプロセッサでデバッグまたはクロスプラットフォームビルドのさまざまなビルドタイプを定義する必要があることは理解できますが、任意の置換を定義する機能は、すでに難しい言語をさらに理解しにくくするためにのみ役立つようです。
[〜#〜] c [〜#〜]にこのような複雑なプリプロセッサが導入されたのはなぜですか?そして、誰かがそれを#debugスタイルの条件付きコンパイルの単純な以外の目的でまだ使用されているように思われる理由を理解させる使用例を持っていますか?
編集:
いくつかの答えを読んだのですが、まだ理解できません。最も一般的な答えは、インラインコードです。インラインキーワードが実行しない場合、実行しない正当な理由があるか、実装を修正する必要があります。 「本当にこのコードをインライン化する」ことを意味するまったく異なるメカニズムが必要な理由がわかりません(インライン化が行われる前に書かれていたコードは別として)。また、「関数に入れるにはあまりにも愚かな場合」という言及された考えを理解していません。確かに、入力を受け取って出力を生成するコードは、関数に入れるのが最適です。私は[〜#〜] 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
でそれらを実装する方法を比較することで、この問題に関する私の見解をほぼまとめています。
C
が発明されたとき、コンパイラ技術は原始的でした。テキストマクロプリプロセッサをフロントエンドにインストールすることは、多くの強力な機能を追加するための簡単で簡単な方法でした。プログラムのサイズと複雑さの増大は、これらの機能には多くの固有の問題が伴うことを示しています。D
にはプリプロセッサがありません。ただし、D
は、同じ問題を解決するためのよりスケーラブルな手段を提供します。
プリプロセッサマクロは、C
に強力な機能と柔軟性を追加します。しかし、彼らには欠点があります:
#include
'するとき、不注意なマクロ展開を避けることは問題になります。C++
のテンプレートで導入されました。)以下は、マクロの一般的な使用法と、Dの対応する機能の列挙です。
リテラル定数の定義:
C
プリプロセッサの方法
#define VALUE 5
D
ウェイ
const int VALUE = 5;
値またはフラグのリストの作成:
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;
関数呼び出し規約の設定:
C
プリプロセッサの方法
#ifndef _CRTAPI1
#define _CRTAPI1 __cdecl
#endif
#ifndef _CRTAPI2
#define _CRTAPI2 __cdecl
#endif
int _CRTAPI2 func();
D
ウェイ
呼び出し規約はブロック単位で指定できるため、関数ごとに変更する必要はありません。
extern (Windows)
{
int onefunc();
int anotherfunc();
}
単純な汎用プログラミング:
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サイト にはさらに多くの例があります。
これらはC上にあるプログラミング言語(より単純な言語)であるため、コンパイル時にメタプログラミングを行うのに役立ちます。つまり、Cコードを生成するマクロコードをより少ない行と時間で作成できます。 Cで直接記述します。
また、「ポリモーフィック」または「オーバーロード」の「関数のような」式を記述するのにも非常に便利です。例えば次のように定義された最大マクロ:
#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;}
...
必要な場合でも、関数をオーバーロードできないためです。
条件付きコンパイルとファイルを含むことは言うまでもありません(これもマクロ言語の一部です)...
マクロを使用すると、コンパイル時にプログラムの動作を変更できます。このことを考慮:
コンパイル時には、マクロプリプロセッサと統合されている限り、未使用のコードはバイナリに入らず、ビルドプロセスが値を変更できることを意味します。例: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を記述しながら、独自のデータ構造とコーディングコンストラクト(クラスとテンプレートを含む!)を定義できます。
プリプロセッサマクロは、実際にはコンパイル時に実行される Turing-complete 言語を提供します。これの印象的な(そして少し怖い)例の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 << 1)
#define BIT_2(1 << 2)
#define BIT_3(1 << 3)
#define BIT_4(1 << 4)
...
#define BIT_28(1 << 28)
#define BIT_29(1 << 29)
#define BIT_30(1 << 30)
#define BIT_31(1 << 31)しかし、なぜ32個の定数を定義するという面倒なことをするのでしょうか? C言語には、パラメーター化されたマクロもあります。本当に必要なのは:
#define BIT(x)(1 <<(x))
とにかく、元のコードを書いた人が電卓を使ったのか、それともすべてを紙の上で計算したのだろうか。
これは、マクロの使用法の1つにすぎません。
すでに述べたことに追加します。
マクロはテキスト置換で機能するため、関数を使用して実行できない非常に便利なことを実行できます。
ここで、マクロが本当に役立ついくつかの例を示します。
_/* 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はalwaysすべての要素を反復処理します。
メモリと文字列を比較する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!")) {
/* ... */
}
_
意図はもっと明確です!
これらは、関数が達成できないことのためにマクロが使用される場合です。マクロは関数を置き換えるために使用するべきではありませんが、他の優れた用途があります。
マクロが本当に輝くケースの1つは、マクロを使用してコード生成を行うときです。
以前は、プラグインにパラメーターを渡す独自の方法でプラグインシステムを使用していた古いC++システムで作業していました(カスタムマップのような構造を使用)。いくつかの簡単なマクロを使用してこの癖に対処し、プラグインで通常のパラメーターを使用して実際の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;
}
あなたの質問のコメントを考えると、関数を呼び出すとかなりのオーバーヘッドが発生する可能性があることを十分に理解していないかもしれません。パラメータとキーレジスタは、途中でスタックにコピーし、途中でスタックを解く必要があります。これは特に古いIntelチップに当てはまりました。マクロを使用すると、プログラマは関数の抽象化を(ほぼ)維持できますが、関数呼び出しのコストのかかるオーバーヘッドを回避できます。 inlineキーワードは助言的ですが、コンパイラーが常に正しいとは限りません。 「C」の栄光と危険は、通常コンパイラを意のままに曲げることができることです。
パンとバターでは、この種のマイクロ最適化(関数呼び出しを回避する)をプログラミングする日常的なアプリケーションは一般に役に立たないほど悪いですが、オペレーティングシステムのカーネルによって呼び出されるタイムクリティカルな関数を記述する場合は、それは大きな違いを生むことができます。
コードをインライン化し、関数呼び出しのオーバーヘッドを回避するのに適しています。また、多くの場所を編集せずに後で動作を変更する場合に使用します。複雑なものには役立ちませんが、インラインにしたい単純なコード行にとっては悪くありません。
マクロを使用すると、コピーアンドペーストされたフラグメントを取り除くことができます。これは他の方法で削除することはできません。
たとえば(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
は、スクリプトの文字列およびコンパイラのフィールド名として使用されます。それは悪いですか?はい。コードをリファクタリングし、LocalName
をRelativeFolderName
に名前変更し(私がしたように)、文字列で同じことを忘れた場合(私がしたように)、スクリプトはあなたがしない方法で動作します ' (実際、私の例では、別のスクリプトファイルでフィールドの名前を変更するのを忘れたかどうかに依存しますが、スクリプトがシリアル化に使用される場合、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);
}
残念ながら、これは他の種類のバグの扉を開きます。コンパイラはすべての前処理の後にどのように見えるかを示していないため、マクロを記述するタイプミスを行うことができ、甘やかされたコードを決して見ることはありません。他の誰かが同じ名前を使用することもできます(だからこそ、#undef
)。だから、賢明にそれを使用してください。コピーペーストされたコード(関数など)を取り除く別の方法を見つけた場合は、その方法を使用してください。マクロを使用してコピー&ペーストされたコードを削除しても結果が得られない場合は、コピー&ペーストされたコードを保持してください。
Cプリプロセッサのテキスト操作を活用することにより、Cに相当する多態的なデータ構造を構築できます。この手法を使用すると、特定の実装の仕様ではなくC構文を利用するため、任意のCプログラムで使用できるプリミティブデータ構造の信頼できるツールボックスを構築できます。
データ構造を管理するためにマクロを使用する方法の詳細な説明はここにあります- http://multi-core-dump.blogspot.com/2010/11/interesting-use-of-c-macros-polymorphic.html
明白な理由の1つは、マクロを使用することにより、コンパイル時にコードが展開され、呼び出しのオーバーヘッドなしで擬似関数呼び出しを取得できることです。
それ以外の場合は、シンボリック定数にも使用できるため、1つの小さなことを変更するために複数の場所で同じ値を編集する必要はありません。
マクロ..&#(* $&コンパイラが何かをインライン化することを拒否する場合。
それはやる気を起こさせるポスターになるはずですよね?
すべての深刻さで、google プリプロセッサ乱用 (同様のSO#1結果として質問が表示される場合があります)。機能を超えたマクロを作成している場合assert()の場合、通常、コンパイラが実際に同様の関数をインライン化するかどうかを確認しようとします。
他の人は、条件付きコンパイルに#ifを使用することに反対します。
if (RUNNING_ON_VALGRIND)
のではなく
#if RUNNING_ON_VALGRIND
.. if()は表示できますが、デバッガでは#ifは表示できないため、デバッグ目的で使用します。次に、#ifdef vs #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つのライナー)用。マクロ(およびインライン関数)のような関数は、前処理(またはインラインの場合はコンパイル)中に行われるコード置換であり、メモリに存在する実際の関数ではないため、プログラムの実行中にいくつかの利点を得ることが目標です。そのため、関数呼び出しのオーバーヘッドはありません(詳細はリンクされたページにあります)。