回答:
コンパイラが適切なマシンコードではなくアセンブリを生成する他の理由は次のとおりです。
add eax,2
変換できます。83 c0 02
66 83 c0 02
use16
コンパイラは通常、高レベルのコードを機械語に直接変換しますが、モジュール方式でビルドして、一方のバックエンドが機械コードともう一方のアセンブリコード(GCCなど)を生成できるようにします。コード生成フェーズは、機械語の内部表現である「コード」を生成します。これは、機械語やアセンブリコードなどの使用可能な形式に変換する必要があります。
歴史的に、多くの注目すべきコンパイラがマシンコードを直接出力していました。ただし、そうすることにはいくつかの困難があります。一般に、コンパイラが正常に動作していることを確認しようとする人は、マシンコードよりもアセンブリコードの出力を調べる方が簡単です。さらに、1パスのCまたはPascalコンパイラを使用してアセンブリ言語ファイルを作成し、2パスアセンブラを使用して処理することも可能です(歴史的に一般的でした)。コードを直接生成するには、2パスのCまたはPascalコンパイラを使用するか、シングルパスコンパイラの後にフォワードパッチアドレスをバックパッチする手段を使用する必要があります(ランタイム環境が起動プログラムのサイズを固定スポット、コンパイラは、コードの最後にパッチのリストを作成し、実行時にスタートアップコードにそれらのパッチを適用させることができます。このようなアプローチでは、パッチポイントごとに実行可能ファイルのサイズが約4バイト増加しますが、プログラム生成の速度は向上します。
目標が、迅速に実行されるコンパイラーを持つことである場合、直接コード生成はうまく機能します。ただし、ほとんどのプロジェクトでは、アセンブリ言語コードを生成してそれを組み立てるコストは、今日では大きな問題ではありません。コンパイラーが他のコンパイラーによって生成されたコードとうまくやり取りできる形式でコードを生成することは、通常、コンパイル時間の増加を正当化するのに十分な大きな利点です。
同じ命令セットを使用するプラットフォームでさえ、異なる再配置可能オブジェクトファイル形式を持つ場合があります。「a.out」(初期のUNIX)、OMF、MZ(MS-DOS EXE)、NE(16ビットWindows)、COFF(UNIX System V)、Mach-O(OS XおよびiOS)、およびELF(Linuxなど)、および32ビットWindows上のXCOFF(AIX)、ECOFF(SGI)、COFFベースのPortable Executable(PE)などのバリアント。アセンブリ言語を生成するコンパイラは、オブジェクトファイル形式について多くの知識を必要としないため、アセンブラとリンカはその知識を別のプロセスにカプセル化できます。
こちらもご覧ください スタックオーバーフローでのOMFとCOFFの違い。
通常、コンパイラーは内部的に命令のシーケンスを処理します。各命令は、その操作名、オペランドなどを表すデータ構造で表されます。オペランドがアドレスの場合、それらのアドレスは通常、具体的な値ではなく、シンボリック参照になります。
アセンブラーの出力は比較的簡単です。コンパイラの内部データ構造を取得し、それを特定の形式のテキストファイルにダンプすることはほとんど問題です。また、アセンブラーの出力は比較的読みやすく、コンパイラーの動作を確認する必要がある場合に役立ちます。
バイナリオブジェクトファイルの出力は、非常に手間がかかります。コンパイラの作成者は、すべての命令がどのようにエンコードされるかを知る必要があります(一部のCPUSでは些細なことではありません)、いくつかのシンボリック参照をプログラムカウンター相対アドレスに変換し、その他をバイナリオブジェクトファイルの何らかのメタデータに変換する必要があります。システム固有の形式ですべてを書き出す必要があります。
はい、中間ステップとしてアセンブラを書き出すことなく、バイナリオブジェクトを直接出力できるコンパイラを作成できます。ソフトウェア開発における非常に多くのことのような質問は、コンパイル時間の短縮が追加の開発および保守作業の価値があるかどうかです。
私が最もよく知っているコンパイラ(freepascal)は、すべてのプラットフォームでアセンブラを出力できますが、プラットフォームのサブセットでのみバイナリオブジェクトを直接出力できます。
コンパイラーは、プログラマーの利益のために、通常の再配置可能なコードに加えて、アセンブラー出力を生成できる必要があります。
LSI-11マシン上のUnix System Vで実行されているCプログラムにバグが見つからないことがあります。何も機能していないようです。最後に必死になって、私はprotable Cコンパイラに翻訳のアセンブラバージョンを排泄させました。ついにバグを発見しました!コンパイラは、マシンに存在するよりも多くのレジスタを割り当てていました!(コンパイラーは、レジスターR0からR7のみを備えたマシンでレジスターR0からR8を割り当てました。)コンパイラーのバグを回避することができ、プログラムは機能しました。
アセンブラー出力のもう1つの利点は、異なるパラメーター受け渡しプロトコルを使用する「標準」ライブラリーを使用しようとすることです。後のCコンパイラでは、パラメータを使用してプロトコルを設定できます(「pascal」は、順序を逆にするC標準ではなく、指定された順序でパラメータをコンパイラに追加させます)。
さらに別の利点は、プログラマーがコンパイラーが行っている恐ろしい仕事を見ることができることです。単純なCステートメントには、約44の機械語命令が必要です。値はメモリからロードされ、すぐに破棄されます。などなど...
個人的には、再配置可能なオブジェクトモジュールの代わりにコンパイラを使用するのは本当にばかげていると思います。プログラムのコンパイル中に、コンパイラはプログラムに関する多くの情報を収集します。通常、このすべての情報は、シンボルテーブルと呼ばれるものに保存されます。アセンブラコードを排泄した後、このすべての情報テーブルをスローします。次に、アセンブラは排泄されたコードを調べ、コンパイラがすでに持っていた情報の一部を再収集します。ただし、アセンブラはForステートメントまたはWhileステートメントのIfステートメントについて何も知りません。したがって、この情報はすべて欠落しています。次に、アセンブラーは、コンパイラーが作成しなかった再配置可能オブジェクトモジュールを作成します。
なぜ???