それぞれのOS(ターゲットとなっているOS)のソースコードをコンパイルする代わりに、一度コンパイルしてどこでも実行できます。
この質問のために、私はそれをVMと呼びます(たとえば、Javaと.NETの両方の場合)。したがって、プログラムの実行は次のようになります。お気に入り
------------ ---- ----
| Executable | -> | VM | -> | OS |
------------ ---- ----
それは完全に理にかなっています、コンパイラはそれぞれのVMのために一般的なままです。ただし、VMの実装は、インストールするマシン、つまり(* nix、windows、mac)x(32ビット、64ビット)によって異なります。
私の質問は、VMそれぞれのマシンに対して書くのではなく、なぜその特定のマシンに対してコンパイラが書かれていないのですか?これにより、それぞれのVM =それぞれのコンパイラをダウンロードすると、そのコンパイラがその特定のマシンのmachine-code + OSを処理します。その結果、任意のマシンのネイティブコードが実行されます。もちろん、各ソースコードはその特定のマシン用にコンパイルする必要がありますが、今は日々、自動化されたシステム、scmビルドはこのことを行うのに役立ちます。
混乱している私の理由は正しいですか、またはここでいくつかの専門知識が不足していますか?
編集:
[〜#〜]移植性[〜#〜]:
はい、それは1つの理由ですが、今日の自動化システムでは移植性が大きな問題ですか?他のマシン用にコンパイルする必要がないという事実をどれくらいの頻度で心配する必要がありますか?ネイティブマシン用にコンパイルされたコードを使用すると、パフォーマンスが大幅に向上します。たとえば、Javaの場合、Windowsで低レベルのプログラミングを行うことはできず、JNIを選択する必要があります。
TeamCity/Jenkinsなどの自動化システムを利用してください。バージョン管理を介して送信されたコードが実行可能ファイルになるような自動化されたシステムセットアップを使用できます。
私の質問は、VMそれぞれのマシンに対して書くのではなく、なぜその特定のマシンに対してコンパイラーが書かれていないのですか?
そのため、ポータブルな実行可能ファイルはもうありません。プラットフォームごとに1つの実行可能ファイルがあります。ユーザーは、プラットフォーム用の特定の実行可能ファイルをダウンロードするか、特定のプラットフォームでコードをコンパイルする必要があります。
VMを使用すると、各プラットフォームはそのプラットフォーム固有のVMを必要とするだけで、同じ実行可能ファイルをすべてのプラットフォームに配布できます。
これらのVMで何か巨大なものがありません。彼らはあなたが言うことを正確に行いますが、自動的に行います。これはJust-In-Timeコンパイラと呼ばれ、.NET(Windowsの場合)とJavaがネイティブにコンパイルされたC++コードの速度に非常に近い理由です。
Java/C#ソースコードはバイトコードに変換されます。このバイトコードは、現在実行されているマシンのマシンコードにコンパイルされます。ほとんどの場合、VMは、バイトコードを再処理する代わりにネイティブコードを実行します。
私はかなりスキップしてプロセスを単純化しましたが、VMはかなりの量の作業を行います。
コンピューターサイエンスの多くの概念と同様に、VMは抽象化レイヤーを提供します。「インターフェイス」(バイトコード/中間言語)に対してコードを記述し、抽象化レイヤー(VM)は実装の詳細(ターゲットマシン用にコンパイルするなどのタスク)。抽象化レイヤーは、実装の詳細を気にする必要のない一連のサービスを提供します。この場合、以下の詳細について心配する必要はありません。抽象化層サービス(VM)が存在する場合、基盤となるハードウェアクライアントは同様の方法で恩恵を受けます-抽象化層を使用できるというだけで、プラットフォームに関する詳細を知る必要はありません。
もちろん、抽象化にはトレードオフがあります。抽象化の反対側の詳細を細かく制御できなくなり、その抽象化に頼って賢明な実装を採用する必要があります。トレードオフに対する抽象化を使用して、潜在的な利益を検討する必要があります。特定のアプリケーションでは、欠点がメリットを上回る可能性があります。すべてのプラットフォームでその高レベルの制御が必要になる場合があります。
異なるプラットフォームへのコンパイルに自動化システムを使用するというあなたの提案は、.NETおよびJava抽象化レイヤーがすでに行っていることとまったく同じです。JITコンパイルの利点の1つは、コンパイラーがこれにより、開発者のマシン上でリリースビルドを実行する場合には不可能であった最適化の可能性がもたらされる可能性があります。
ネイティブコードの生成とプログラムの実行が同時に発生するために実行時の速度低下が心配な場合は、プログラムを最初に実行するときではなく、インストール中にネイティブコードを生成するオプションがあります。 .NETは nGen をこの目的で提供し、実行時ではなくインストール時にネイティブコード生成を実行できます。 CLRは、JITコンパイルを実行するのではなく、キャッシュされたネイティブコードを使用します。
これは非常に単純化しすぎています。クロスプラットフォームは、特定のネイティブ命令セット用にコンパイルするだけではありません。 APIとライブラリが異なるため、同じC/C++コードを異なるプラットフォームで単に再コンパイルすることはできません。たとえば、WindowsとUbuntu LinuxではGUI開発が大きく異なり、UnixとIBM z/OSではソケット通信がおそらく同じではありません。
したがって、「1回書き込み、どこでもコンパイル(およびリンク)」するためには、ある種の抽象化レイヤーを作成する必要があります。すべてのOSレベルのサービスに共通のAPI。次に、これを実装して、サポートするすべてのプラットフォームに配布する必要があります。
また、今日のメモリモデルはますます類似していますが、異なるプラットフォーム間でもいくつかの違いがあるため、メモリ割り当ても抽象化する必要があります。ここで最も簡単なのは、ネイティブのメモリマネージャの上に独自の「仮想」メモリマネージャを作成することです。
その間、ファイルシステムとアクセス制御(ACL)は、たとえば、UnixとWindowsでかなり異なる方法で処理されるため、それらも抽象化する必要があります。おそらく、基礎となる実装用に独自の「ファイル」ラッパーを作成することもできます。
次に、スレッド化します。 Linuxにはプロセスしかなく、Windowsにはスレッドがあるので、これも何らかの形で抽象化する必要があります。
さて、この時点で仮想マシンはほぼ構築されました。ここでのポイントは、さまざまなプラットフォーム用の任意のコードのコンパイルはかなり簡単ですが、あらゆるプラットフォームで実行できる複雑さの作業用ソフトウェアのビルドは簡単なことではありません。
私の質問は、それぞれのマシンに対してVMを書く代わりに、なぜその特定のマシンのためにコンパイラが書かれていないのですか?
これがそれが機能する方法であると少し想像してみましょう。私はJavaアプリケーションを作成し、それをコンパイルします。コンパイラは、サポートされているすべてのプラットフォーム用の実行可能ファイルを生成します。現在、次の実行可能ファイルがあります。
などなど.
次に、これらすべてを自分のWebサイトにアップロードし、それぞれにダウンロードリンクを提供します。次に、これらすべてをdownload.com、tucows.com、sofpedia.comに送信し、それぞれを個別に送信します。
これは私にとって大きな痛みのようです!ソフトウェアをバージョン1.1に更新すると、このプロセスを繰り返す必要があります。
そして、新しいプラットフォームが出現するとどうなりますか?現在、私のソフトウェアはこのプラットフォームで使用できません。サポートするように開発者ソフトウェアを更新しない限り、再コンパイルしてから、この新しいプラットフォーム用にソフトウェアを再リリースします。
そして、ユーザーはどうですか?彼らは私のサイトにアクセスし、自分のプラットフォームに使用するソフトウェアの正確なバージョンをリストから選択する必要があります。ほとんどのユーザーはx86とx64のどちらを実行しているかわからないため、おそらく間違ったバージョンをダウンロードして何らかのエラーを受け取ることになります。その後、WindowsからMacに切り替えた場合、戻ってソフトウェアを再ダウンロードする必要があります。
これはすべて、開発者とユーザーの両方にとって大きな痛みです。これは、Javaとまったく同じように、バイトコードにコンパイルしてVMで実行するだけで回避できます。
パフォーマンスがネイティブコードの実行とほぼ同じである場合、問題は、なぜネイティブコードにコンパイルしないのか、なぜわざわざネイティブコードにコンパイルするのかということです。
VM/JITアプローチのもう1つの大きな利点は、バイナリ互換性です。たとえば、クラスAを含むアセンブリ/ JAR/DLLと、クラスAから派生するクラスBを含む別のアセンブリがあるとします。クラスAを変更するとどうなりますか。あなたはプライベートメンバーを追加しますか?プライベートメンバーを追加してもクラスBにはまったく影響がありませんが、Bを含むアセンブリが既にネイティブコードにコンパイルされている場合、ネイティブコードがAの古いメモリレイアウトなので、たとえばAの変数に予約するメモリが少なすぎるため、Aのメンバーと追加されたメンバーBは同じメモリロケーションにマップされます。
一方、VM=を使用している場合、コードをネイティブコードにコンパイルするとすべてのクラスのレイアウトがわかるため、これらの問題はすべてなくなります。
あなたの投稿の編集で、あなたは移植性の有用性、VMについての疑問に疑問を投げかけます。
私の職業生活では、移植性はthe最も重要な要素でした-私の展開プラットフォームは、私が展開するプラットフォームとほとんど同じではないため、Windowsで開発およびテストし、QAに渡すことができますLinuxでテストし、Solarisの本番環境にデプロイする部署。
これは、単に孤立して考案された例ではありません。これは、おそらく私の過去数十年のキャリアの過去12年間、私の専門家としての存在の大部分を占めてきました。同じまたは類似の経験を持つ他の多くがいると確信しています。
(同じソースコードで)異なる(クロス)コンパイラーを使用して異なるバイナリーを作成した場合、それぞれが独自のOS /アーキテクチャーでテストされていないと、各バイナリーの品質に自信が持てません。
素晴らしいオープンソースのFreePascalコンパイラプロジェクトには、「一度だけ書き込み、どこでもコンパイルする」というタグラインがあります。これは事実ですが、評判の良いソフトウェア会社がそうし、すべてのプラットフォームで徹底的にテストせずにアプリケーションをリリースすることは期待できません。
簡単:テスト。
コードが仮想マシンで正常に動作する場合、VMが動作していることが証明されている他のハードウェアで実行されると確信できます。
異なるプラットフォーム用にコンパイルする場合、すべてのプラットフォームには独自の癖があるため、実行するテストがたくさんあります。
コードをコンパイルできることだけでなく、移植性以外にもさまざまなことが可能です。
Cプログラムの動作は、プラットフォームによって異なる場合があります。
int i;
char c[6];
int * p;
p = &c[2];
p = p + 1;
IBM Powerチップでは問題なく動作しますが、SPARCチップでは例外があります。
OS、OSバージョン、ファイルシステム環境変数のレジストリ設定はすべて、コンパイルされたプログラムの実行方法に影響を与える可能性があります。
JVMは、ハードウェアとソフトウェアの環境に関係なく、ほとんど同じ動作を保証します。