私は Java および VB.Net プログラマーとして約 4 年間、C# プログラマーとして約 6 か月間働いています。また、Perl、Python、PHP、JavaScript などの一連の動的言語も使用しました。
プリプロセッサが必要になったことは一度もありません。
私の質問は、C、C++、および Objective-C ではプリプロセッサがこれほど広範囲に使用されているのに、Java、C#、または Scala などの言語ではめったに (またはまったく) 見られないのはなぜですか?
私は Java および VB.Net プログラマーとして約 4 年間、C# プログラマーとして約 6 か月間働いています。また、Perl、Python、PHP、JavaScript などの一連の動的言語も使用しました。
プリプロセッサが必要になったことは一度もありません。
私の質問は、C、C++、および Objective-C ではプリプロセッサがこれほど広範囲に使用されているのに、Java、C#、または Scala などの言語ではめったに (またはまったく) 見られないのはなぜですか?
私は Objective-C を知らないので、私の答えは C と C++ でのプリプロセッサの使用を対比することです。
プリプロセッサは、いくつかの理由から、もともと C に必要でした。私の記憶が正しければ、もともと C には定数がなかっ#define
たため、マジック ナンバーを回避するために定数が必要でした。1999 年以前は、C にはインライン関数がなかった#define
ため、コードの構造を維持しながら関数呼び出しのオーバーヘッドを節約するために、マクロまたは「疑似関数」を作成するために再び使用されました。また、C には実行時またはコンパイル時のポリモーフィズムがない#ifdef
ため、条件付きコンパイルには s が必要でした。コンパイラは通常、到達不能なコードを最適化するほど賢くありませんでした。そのため、ここでも#ifdef
s を使用してデバッグ コードや診断コードを挿入していました。
C++ でプリプロセッサを使用することは、C への逆戻りであり、一般的に嫌われています。定数、インライン関数、テンプレートなどの言語機能は、C でプリプロセッサを使用するほとんどの状況で使用できます。
C++ でのプリプロセッサの使用が許容される、または必要でさえある少数のケースには、同じヘッダーが複数回含まれないようにするため#ifdef __cplusplus
、C と C++ の両方に同じヘッダーを使用するため、および__FILE__
ヘッダーファイルのガードが含まれます。__LINE__
ロギング、およびその他のいくつか。
プリプロセッサは、プラットフォーム固有の定義にもよく使用されますが、Stephen Dewhurst によるC++ Gotchasは、プラットフォーム固有の定義用に個別のインクルード ディレクトリを用意し、プラットフォームごとに個別のビルド構成でそれらを使用することを推奨しています。
Java、C#、または Scala で使用されているプリプロセッサが表示されない理由は、これらの言語には明らかにプリプロセッサがないためです。
C プリプロセッサの一般的な用途の 1 つは、プラットフォーム固有のコードを提供することです。C (ここでは C++ と Objective-C を含めます) は、オペレーティング システムと直接やり取りする必要がある低レベル言語であるため、移植可能なコードでは、さまざまなオペレーティング システム用にコンパイルされたコードのさまざまなセクションが必然的に存在する必要があります。zlibなどの成熟した移植性の高いコード ベースで、この種の広範な例を見つけることができます。
簡単な例として、ネットワーク ソケットを閉じるには、次のようにする必要があります (あるレベルでは、これは確かに関数にラップできますが、どこかに存在する必要があります)。
#ifdef WIN32
closesocket(s);
#else
close(s);
#endif
VM で実行される新しい言語は、コードのプラットフォーム固有のさまざまなセクションを必要とせず、単一の移植可能な標準ライブラリに対して記述できます。
プリプロセッサは、C で定数を定義する方法も提供します。これは、新しい言語の他の優れた言語機能によって提供されます。
The Design and Evolution of C++ で、Bjarne Stroustrup は、C++ のプリプロセッサへの依存を取り除きたいと述べましたが、成功しませんでした。
すべての言語には、個別にコンパイルするためのメカニズムが必要です。理想的には、言語はインターフェースを実装から区別し、モジュールはエクスポートするモジュールのインターフェースのみに依存します。(たとえば、Ada、Clu、Modula などを参照してください。)
C には、インターフェースまたは実装のための言語構造がありません。異なる .c ファイルがインターフェイスの 1 つのビューを共有することが重要であるため、プログラミング規則は、.h ファイルに宣言 (つまり、インターフェイス) を配置し、テキストのインクルージョン ( #include
) を使用してそれらの宣言/インターフェイスを共有するように進化しました。原則として、#define
省略#ifdef
できますが、#include
できませんでした。
今日、言語設計者は、テキストを含めることは鉄道を走らせる方法ではないことを認識しているため、言語は、個別にコンパイルされたインターフェース (Ada、Modula、OCaml)、コンパイラによって生成されたインターフェース (Haskell)、またはインターフェースの一貫性を保証する動的システムのいずれかに対して実行される傾向があります。 (Java、スモールトーク)。このようなメカニズムがあれば、プリプロセッサは必要ありません。プリプロセッサを使用しない理由はたくさんあります (ソース コードの分析とデバッグを考えてみてください)。
これらの言語の設計と目的は同じではないためです。
C は強力なツールとしてプリプロセッサを念頭に置いて構築され、非常に基本的なもの (インクルージョン ガードなど) を実装するために使用され、開発者はそれを使用して、マクロを使用してコードを最適化したり、オプションで特定のブロックを含めたり除外したりできました。他のものに加えてコード。C++ は C のイディオムのほとんどを継承し、マクロはスピードのために使用されなくなりました (インラインが導入されたため) 。
Gosling と Heilsberg はどちらも、前処理の誤用によって生じる危険と技術的負債を理解しているからです!
前処理は、Java の世界では非常に一般的です。これは、組み込みの適切な抽象化機能が言語に不足していることを補うために使用されます。そうしないと、ボイラープレート コードを無限にコピー アンド ペーストすることになります。
多くの人がこれが真実であることに気付いていない理由は、Java の世界ではそれが「前処理」ではなく「コード生成」と呼ばれているためです。「プリプロセッサ」は厄介な古い C のように聞こえますが、「コード生成」はプロのツールのように聞こえます。成熟した企業プロセスを効率化します。ただし、言語に組み込まれている機能を使用するだけでなく、互換性のない非標準の専用ツールに大金を払わなければならない場合でも、それでも前処理です。
現代の言語ではその cpp が不要であるというコンセンサスのように思われるものには同意しません。同じプログラムのわずかに異なるバージョンが 3 つある場合が多く、バージョンごとに多くの変更を加えたいと考えています。CPP を使用すると、それらをすべて #if #else ブロックに入れることができ、コンパイル行で #if を定義できます。Java では、ある種の静的グローバルを作成し、コンパイル時に初期化する必要があります。私はそれが適切に機能することはありませんでした。
C および C++ のプリプロセッサには 2 つの異なる機能があります
ビルド プロセスでファイルをまとめる - Java などの言語。これを行うには、インポートのような独自のメカニズムがあります
テキスト置換の実行 - これは C ではまだある程度必要ですが、C++ では (ほとんどの場合) テンプレートを使用してより適切に行うことができます
したがって、C と C++ の両方がこれらの最初のものに必要ですが、C++ でさえ役立つ場合がありますが、C++ は 2 番目のもののためにジャンクすることができます -今日の前半のこの質問を参照してください。
現代の言語には、言語自体にプリプロセッサが含まれています。C++ の場合、プリプロセッサはモジュール管理と条件付きインクルードなどにのみ必要です。これは非常に便利です。
今日私たちが知っているように、コンパイラは 1 つのツールではなかったので、それは別のツールだったと思います。非常に古い C コンパイラは、トークンをファイルに生成し、残りのコンパイルを別々の段階で行っていたと聞きました。私が考えることができる主な理由は、メモリやその他のリソースが、現在のものに比べて非常に不足していたことです。
Perl をもう少し詳しく調べてください。Perl は、基本的に Perl で書かれたカスタム Perl プリプロセッサであるソース フィルターをサポートしています :)
現代の言語は C または C++ で書かれており、その実装自体にマクロがあることは確かです。オペレーティングシステムの違いに対処するために必要です。動的/高レベル言語は、マクロが必要な下部のどこかにある多くのものをラップして非表示にします。
また、高速化のためにマクロが使用されることもあります。動的言語では、速度はそれほど重要ではありません。
Java は、C++ を使いにくくするいくつかの機能を回避するように設計されています。
C# は、Java から設計上の決定事項のほとんどをコピー (または継承) します。
高レベルのプログラミング言語は、この種の低レベルのアーティファクトを回避します。