なぜ最近のほとんどのプログラミング言語にマクロが含まれていないのですか?


31

私はそれらがC / C ++で非常に安全に実装されていないことを知っています。より安全な方法で実装することはできませんか?マクロの不利な点は、マクロが提供する強力な力を上回るほどひどいものですか?


4
別の方法ではかなり簡単に達成できないマクロが提供する正確なパワーとは?
チンメイカンチ

2
C#では、単純なプロパティとバッキングフィールドを1つの宣言にラップするためにコア言語拡張が必要でした。これには字句マクロ以上のものが必要です。VisualBasicのWithEvents修飾子に対応する機能をどのように作成できますか?セマンティックマクロのようなものが必要です。
ジェフリーハンティン

3
マクロの問題は、すべての強力なメカニズムと同様に、プログラマーが他の人が行った、または行う予定の前提を破ることができることです。仮定は推論の鍵であり、ロジックについて合理的に推論する能力がなければ、進歩を遂げることは法外になります。
dan_waterworth

2
@Chinmay:マクロはコードを生成します。Java言語にはそのような機能はありません。
ケビンクライン

1
@Chinmay Kanchi:マクロを使用すると、実行時ではなくコンパイル時にコードを実行(評価)できます。
ジョルジオ

回答:


57

主な理由は、マクロがレキシカルだからだと思います。これにはいくつかの結果があります。

  • コンパイラには、マクロが意味的に閉じていること、つまり、関数のように「意味の単位」を表していることをチェックする方法がありません。(考慮#define TWO 1+1TWO*TWO同等のものは何ですか?3.)

  • マクロは関数のように型付けされていません。コンパイラは、パラメータと戻り値の型が意味をなすことを確認できません。マクロを使用する展開式のみをチェックできます。

  • コードがコンパイルされない場合、コンパイラは、エラーがマクロ自体にあるのか、マクロが使用されている場所にあるのかを知る方法がありません。コンパイラは、半分の時間で間違った場所を報告するか、おそらくどちらかが正常であっても両方を報告する必要があります。(考えてみましょう#define min(x,y) (((x)<(y))?(x):(y)):の種類の場合、コンパイラは何をすべきxとはy一致していないか、実装していませんoperator<?)

  • 自動化ツールは、意味的に有用な方法でそれらと連携することはできません。特に、関数のように機能するが式に展開されるマクロ用のIntelliSenseのようなものを持つことはできません。(再び、min例。)

  • マクロの副作用は、関数の場合ほど明確ではないため、プログラマに混乱を招く可能性があります。(min例をもう一度検討してください:関数呼び出しでxは、式for が1回しか評価されないことがわかりますが、ここではマクロを見ずに知ることができません。)

私が言ったように、これらはすべてマクロが字句的であるという事実の結果です。それらをより適切なものに変えようとすると、関数と定数になります。


32
すべてのマクロ言語が語彙的ではありません。たとえば、Schemeマクロは構文的であり、C ++テンプレートはセマンティックです。字句マクロシステムには、構文を意識せずに任意の言語に追加できるという特徴があります。
ジェフリーハンティン

8
@Jeffrey:私の「マクロは字句的」は、「人々がプログラミング言語でマクロを参照するとき、一般的に字句的マクロを考える」という略語だと思います。Schemeがこのロードされた用語を根本的に異なるものに使用するのは残念です。ただし、C ++テンプレートは、完全に字句的ではないため、おそらくマクロとは広く呼ばれていません。
ティムウィ

25
Schemeのマクロという用語の使用はLispにまでさかのぼると思います。これはおそらく、他のほとんどの使用法よりも前のものであることを意味します。IIRC C / C ++システムはもともとプリプロセッサと呼ばれていました。
ベヴァン

16
@Bevanは正しい。マクロが語彙的であると言うのは、あなたが最もよく知っている鳥はペンギンだから、鳥は飛べないということです。とはいえ、あなたが提起するポイントのほとんど(すべてではない)は、構文マクロにも当てはまりますが、程度は低いかもしれません。
ローレンスゴンサルベス

13

しかし、はい、マクロ C / C ++よりも優れた設計と実装が可能です。

マクロの問題は、マクロが事実上、コードを別のものに書き換える言語構文拡張メカニズムであることです。

  • C / C ++の場合、基本的な健全性チェックはありません。注意すれば、問題はありません。間違えたり、マクロを使いすぎたりすると、大きな問題が発生する可能性があります。

    これに加えて、(C / C ++スタイルの)マクロでできる多くの簡単なことは、他の言語で他の方法で行うことができます。

  • さまざまなLisp方言のような他の言語では、マクロはコア言語構文とよりよく統合されますが、マクロの「漏れ」の宣言で問題が発生する可能性があります。これは、衛生的なマクロによって対処されます


歴史的背景

マクロ(マクロ命令の略)は、アセンブリ言語のコンテキストで最初に登場しました。ウィキペディアによると、マクロは1950年代に一部のIBMアセンブラーで利用可能でした。

元のLISPにはマクロはありませんでしたが、1960年代半ばに初めてMacLispに導入されました:https : //stackoverflow.com/questions/3065606/when-did-the-idea-of-macros-user-defined-code -transformation-appearhttp://www.csee.umbc.edu/courses/331/resources/papers/Evolution-of-Lisp.pdf。それ以前は、「fexprs」はマクロのような機能を提供していました。

Cの初期バージョンにはマクロがありませんでした(http://cm.bell-labs.com/cm/cs/who/dmr/chist.html)。これらは、プリプロセッサを介して1972-73年頃に追加されました。それ以前は、Cはとのみをサポート#includeしていました#define

M4マクロプリプロセッサは1977年頃に誕生しました。

より最近の言語では、動作のモデルがテキストではなく構文であるマクロを実装しているようです。

したがって、誰かが「マクロ」という用語の特定の定義の優位性について話すとき、その意味は時間とともに進化していることに注意することが重要です。


9
C ++は、2つのマクロシステム(プリプロセッサとテンプレート拡張)に恵まれていますか?
ジェフリーハンティン

テンプレートの展開がマクロであると主張することは、マクロの定義を拡張することだと思います。定義を使用すると、元の質問は無意味になり、単に間違っているだけです。実際、ほとんどの現代言語にはマクロがあります(定義によると)。しかし、圧倒的多数の開発者がマクロを使用するので、マクロを使用するのは良い質問になります。
ダンク

@ Dunk、Lispのようなマクロの定義は、哀れなCプリプロセッサのような「現代の」愚かな理解よりも前のことです。
SKロジック

@SK:「技術的に」正確で会話を難読化する方が良いのか、それとも理解する方が良いのか?
ダンク

1
@Dunk、用語は無知な大群によって所有されていません。彼らの無知は決して考慮されるべきではありません。さもなければ、それはコンピューターサイエンスの全領域を馬鹿にすることにつながります。「マクロ」をCプリプロセッサへの参照としてのみ理解している場合、それは無知に他なりません。OfficeでVBAの「マクロ」であるLispや、pardonnez monfrançaisについても聞いたことのない人がかなりいるとは思いません。
SKロジック

12

スコットが指摘しているように、マクロはロジックを隠すことができます。もちろん、関数、クラス、ライブラリ、その他多くの一般的なデバイスも同様です。

しかし、強力なマクロシステムをさらに活用して、通常は言語にない構文と構造を設計および利用することができます。これは確かに素晴らしいツールです。ドメイン固有の言語、コードジェネレーターなど、すべて単一の言語環境で快適に...

ただし、悪用される可能性があります。コードの読み取り、理解、デバッグが難しくなり、新しいプログラマーがコードベースに慣れるのに必要な時間が長くなり、費用のかかるミスや遅延が発生する可能性があります。

そのため、プログラミングを単純化することを目的とした言語(JavaやPythonなど)にとって、そのようなシステムは忌み嫌われるものです。


3
そしてJavaは、ジェネリック・バイ・消去、... foreachの、注釈、アサーションを追加することにより、レールをオフに行ってきました
ジェフリーHantin

2
@Jeffrey:忘れてはいけない、たくさんのサードパーティのコードジェネレーター。考え直して、それらを忘れましょう。
Shog9

4
Javaが単純ではなく単純化された別の例。
ジェフリーハンティン

3
@JeffreyHantin:foreachの何が問題になっていますか?
Casebash

1
@Dunkこの類推はストレッチかもしれませんが、言語の拡張性はプルトニウムのようなものだと思います。実装に費用がかかり、望ましい形状に機械加工するのが非常に難しく、誤用すると非常に危険ですが、適切に適用すると驚くほど強力です。
ジェフリーハンティン

6

マクロは、状況によっては非常に安全に実装できます。たとえば、Lispでは、マクロは変換されたコードをデータ構造(s-expression)として返す関数にすぎません。もちろん、Lispはそれがホモイコニックであるという事実と「コードはデータである」という事実から大きな恩恵を受けます。

簡単なマクロの例は、例外の場合に使用されるデフォルト値を指定する次のClojureの例です。

(defmacro on-error [default-value code]
  `(try ~code (catch Exception ~'e ~default-value)))

(on-error 0 (+ nil nil))               ;; would normally throw NullPointerException
=> 0                                   ;l; but we get the default value

しかしLispでも、一般的なアドバイスは「必要がない限りマクロを使用しない」です。

ホモイコニック言語を使用していない場合、マクロはより巧妙になり、他のさまざまなオプションにはすべて落とし穴があります。

  • テキストベースのマクロ -Cプリプロセッサなど-実装は簡単ですが、構文上の癖を含むテキスト形式で正しいソース構文を生成する必要があるため、正しく使用するには非常に注意が必要です
  • マクロベースのDSLS -C ++テンプレートシステムなど。複雑で、それ自体がトリッキーな構文になる可能性がありますが、コンパイラーとツールの作成者にとっては、言語の構文とセマンティクスに大きな新しい複雑さをもたらすため、正しく処理するのは非常に複雑です。
  • AST /バイトコード操作API-例えば、Javaリフレクション/バイトコード生成-理論的には非常に柔軟性がありますが、非常に冗長になる可能性があります。3行の関数に相当するコードを生成するのに10行のコードが必要な場合は、メタプログラミングの努力によってあまり得られていない...

さらに、マクロができることはすべて、最終的に他の方法でチューリング完全な言語で実現できます(これが多くの定型文を書くことを意味する場合でも)。このすべてのトリッキーな結果として、多くの言語がマクロは実装するすべての努力の価値がないと判断することは驚くことではありません。


あー これ以上の「コードはデータは良いことです」ごみ。コードはコードであり、データはデータであり、この2つを適切に分離できないと、セキュリティホールになります。 存在する最大の脆弱性クラスの1つとしてのSQLインジェクションの出現は、コードとデータを簡単に交換できるようにするという考えを信用できなかったと思うでしょう。
メイソンウィーラー

10
@Mason-code-is-dataの概念を理解しているとは思わない。すべてのCプログラムのソースコードもデータです。たまたまテキスト形式で表現されています。Lispも同じですが、コンパイル前にマクロで操作および変換できる実用的な中間データ構造(s式)でコードを表現します。どちらの場合も、信頼できない入力をコンパイラに送信することはセキュリティホールになる可能性があります。
-mikera

Rustは、もう1つの興味深い安全なマクロシステムです。C / C ++レキシカルマクロに関連する種類の問題を回避するための厳格なルール/セマンティクスを持ち、DSLを使用して、後で挿入されるマクロ変数に入力式の一部をキャプチャします。入力の関数を呼び出すLispsの能力ほど強力ではありませんが、他のマクロから呼び出すことができる便利な操作マクロの大規模なセットを提供します。
-zstewart

あー これ以上のセキュリティのゴミ。それ以外の場合は、フォンノイマンアーキテクチャが組み込まれたすべてのシステム、たとえば自己修正コードの機能を備えたすべてのIA-32プロセッサを非難します。あなたが物理的に穴を埋めることができるかどうか疑っています...とにかく、あなたは一般的に事実に直面しなければなりません:コードとデータ間の同型はいくつかの方法で世界の性質です。そして、(おそらく)プログラマーは、セキュリティ要件の不変性を維持する義務を負っています。これは、どこかで人為的に早期分離を適用することとは異なります。
FrankHB

4

質問に答えるには、どのマクロが主に使用されているかを考えてください(警告:脳でコンパイルされたコード)。

  • 記号定数を定義するために使用されるマクロ #define X 100

これは次のものに簡単に置き換えることができます。 const int X = 100;

  • (本質的に)インライン型非依存関数を定義するために使用されるマクロ #define max(X,Y) (X>Y?X:Y)

関数のオーバーロードをサポートする言語では、正しい型の関数をオーバーロードすることで、またはジェネリックをサポートする言語でジェネリック関数を使用することで、はるかにタイプセーフな方法でエミュレートできます。このマクロは、コンパイルされる可能性のあるポインタや文字列を含むすべてのものを喜んで比較しようとしますが、ほとんど間違いなくあなたが望んでいたものではありません。一方、マクロをタイプセーフにした場合、マクロはオーバーロードされた関数に比べて利点も利便性も提供しません。

  • よく使用される要素へのショートカットを指定するために使用されるマクロ。 #define p printf

これはp()、同じことをする関数に簡単に置き換えられます。これはC(va_arg()関数ファミリーを使用する必要があります)に非常に関係していますが、可変数の関数引数をサポートする他の多くの言語では、はるかに簡単です。

特別なマクロ言語ではなく言語内でこれらの機能をサポートすることは、コードを読んでいる他の人にとってよりシンプルで、エラーが少なく、混乱がはるかに少ないです。実際、別の方法で簡単に複製できないマクロの単一のユースケースを考えることはできません。唯一の彼らはのような条件付きコンパイル構文に結びついているとき、マクロは本当に便利です場所です#if(など)。

その点については、一般的な言語での条件付きコンパイルに対する非プリプロセッサソリューションは(Javaでのバイトコードインジェクションのように)非常に面倒だと思うので、私はあなたと議論しません。しかし、Dのような言語は、プリプロセッサを必要とせず、プリプロセッサの条件を使用するよりも面倒ではなく、エラーがはるかに少ないソリューションを考え出しました。


1
#define maxが必要な場合は、パラメーターを角かっこで囲んでください。そのため、演算子の優先順位による予期しない影響はありません... #define max(X、Y)((X)>(Y)?(X):( Y))
foo

あなたはそれが単なる例であることに気づきます...意図は説明することでした。
チンメイカンチ

問題を体系的に処理するために+1。条件付きコンパイルは簡単に悪夢になる可能性があることを付け加えます-後処理後に残ったものを可視化することを目的とした「unifdef」(??)というプログラムがあったことを覚えています。
インゴ

7
In fact, I can't think of a single use-case for macros that can't easily be duplicated in another way:少なくともCでは、マクロを使用せずにトークン連結を使用して識別子(変数名)を形成することはできません。
チャールズサルビア

マクロを使用すると、特定のコードとデータ構造を「並列」に保つことができます。たとえば、関連付けられたメッセージに少数の条件があり、それらを簡潔な形式で保持する必要がある場合enum、条件を定義してメッセージを定義する定数文字列配列を使用しようとすると、問題が発生する可能性があります列挙型と配列が同期しなくなります。マクロを使用してすべての(enum、string)ペアを定義し、そのマクロをスコープ内の他の適切な定義とともに2回含めると、各列挙値を文字列の隣に配置できます。
-supercat

2

マクロで私が見た最大の問題は、それらを頻繁に使用すると、見つけるのが簡単な場合とそうでない場合があるマクロのロジックを隠すことができるため、コードの読み取りと保守が非常に難しくなることです)。


マクロを文書化するべきではありませんか?
Casebash

@Casebash:もちろん、マクロは他のソースコードとまったく同じように文書化する必要がありますが、実際にはそれが行われることはめったにありません。
スコットドーマン

3
ドキュメントをデバッグしますか?不完全に記述されたコードを処理する場合、それだけでは不十分です。
ジェフ

OOPは...あまりにもかかわらず、それらの問題のいくつかを持っている
aoeu256

2

C / C ++のMACROは非常に限定的で、エラーが発生しやすく、実際にはそれほど有用ではないことに注意してください。

LISPやz / OSアセンブラー言語などで実装されているMACROは、信頼性が高く、非常に便利です。

しかし、Cの限られた機能の悪用のため、彼らは悪い評判を集めました。だから、誰もマクロを実装しなくなり、代わりに、テンプレートのように使用される単純なもののマクロを実行し、Javaのアノテーションのような、より複雑なもののマクロを実行します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.