フォーラムでは、初心者のC ++プログラマーからの質問をよく目にします。「どのような文学を勧めますか?」 通常、信頼性の高い書籍を追加して答えます。読む本の量は練習に置き換わるものではありません。 あなたは本当に何かをする必要があります。 でも何? 良いプロジェクトは何でしょうか? たくさんのことを教えてくれるものが必要ですが、同時にシンプルで面白くて面白くないものがあります。 私は最近この質問について考えましたが、答えを見つけたようです。 あなたは間違いなくバイトコードインタプリタを書くべきです。 私にとって、そのようなプロジェクトは、その後のキャリア全体を形成する上で重要でした。
すべての始まり
200X年には、大学2年生でした。 プログラミングの経験が少しありました。 C ++で利用可能な抽象化を使用することができましたが、すべてがどのように機能するかを本当に理解していませんでした。 私にとって、コンパイラとオペレーティングシステムは、魔法のおかげで機能する単なるブラックボックスでした。
知識の不足は、プログラミング言語の戦争に積極的に参加することを妨げませんでした。 これらのスレッドの1つにより、Java仮想マシンに精通するようになり、 スタックアーキテクチャについて学びました。
私はx86アーキテクチャについてほとんど何も理解しておらず、他のアーキテクチャについては何も聞きませんでした。 レジスタのないマシンのアイデアは、私にとって非常に興味深く、珍しいように思えました。 その日ずっと考えて、座って自分のシンプルなスタック仮想マシンを書くことにしました。
どうでしたか
愚かな仮想マシン(または略してSVM)は、可能な限り単純なアイデアに従いました。 ワードサイズは32ビットで、メモリはワードでアクセスされました(個々のバイトにアクセスすることは不可能でした)。 プログラムコードとデータ用のメモリ領域は互いに完全に分離されていました(これはハーバードアーキテクチャの特徴であることが後でわかりました)。 スタックでさえ、独自のメモリにありました。
一連の指示も非常に簡単でした。 メモリ、スタック操作、ジャンプを操作する標準の算術および論理命令。 すべてが最も明白な方法で機能しました。 たとえば、 ADD
命令は、スタックから最初の2つの32ビット値を取得し、それらを符号付き整数の形式に入れて、結果をスタックにプッシュしました。
I / Oはプリミティブであり、stdin / stdoutに関連付けられていました。 IN
とOUT
指示がありました。 最初は読み取り結果をスタックにプッシュし、2番目はスタックの最初の値を画面に表示しました。 便宜上、特別なフラグを追加しました。入力を生バイトのストリームと見なすか、符号付き整数の文字列表現と見なすかです。
プログラミングはどんな感じでしたか
最初は、SVM用のすべてのプログラムを16進エディターで純粋なマシンコードで記述しました。 すぐに疲れたので、ラベルと文字列リテラルをサポートするアセンブラーを作成しました。 たとえば、「Hello、World」は次のようになりました。
"Hello, World!\n" print
アセンブラが文字列リテラルを検出すると、スタック上のすべてのバイトをプッシュします。 PRINT
は命令ではなく、ループを生成する単なるマクロです。 ループは、スタックの各文字が0に達するまで印刷します。
スタックマシンのコードの読み書きは奇妙な経験です。 少し高度な例を次に示します。これは、最大公約数の計算方法です。
IN ; "A" IN ; "B" :GCD ; DUP ; B 0, A gcd 0 ; ( ) @END ; ( ) JE ; ( ) SWP ; B 0, gcd(B, A modulo B) OVR MOD @GCD JMP ; ! :END POP ; 0 OUT ; ,
これは、ラベルと条件付きジャンプの使用を示しています。
さらに高度な例をご覧になりたい場合は、 insertsでソートするためのアセンブリコードをお読みください。ただし、スキップすることに決めたとしても問題ありません。 これはかなり長いリストなので、投稿には含めませんでした。
興味がある場合は、古いファイルで見つけた仮想マシンとアセンブラーのコードもご覧ください。 そこに異常なものは何もありません、そして、一般に、それはあなたがバイトコードインタプリタを書く必要がない方法のむしろ例です。 スタックされたマシンをマシンの登録と同じように機能させるには、いくつかのトリックが必要です。 もちろん、私はそれらについて何も知りませんでした。私のアプローチは素朴で、生産性に悪影響を及ぼしました。
なぜあなたもこれをする必要があるのですか
原始的なバイトコードインタプリタとそのためのいくつかのプログラムを記述することで、通常は当たり前のことと考えられているこのようなことについて考えるようになります。
たとえば、SVMでプロシージャを実装することを考えたとき、関数の呼び出しは、呼び出し元と呼び出し先の両方が暗黙的に同意しなければならない追加のルールセットを持つ遷移に過ぎないことに気付きました。 これにより、呼び出し規約の概念を理解することができ、 _cdecl
やWINAPI
などの魔法のようなものが突然意味をなしました。
もちろん、そのようなプロジェクトはC ++やCのすべてのトリッキーなニュアンスを教えてくれるわけではありませんが、正しい考え方を形成するのに役立ちます。 その年、私はある種の精神的な障壁を抱えていました。そのため、魔法に満ちた黒い箱の中を見る勇気はありませんでした。 プログラミングに真剣に取り組む予定の場合、特にC ++やCなどの言語での低レベルプログラミングに興味がある場合は、この障壁を打破することが非常に重要です。逆アセンブラー。 それは私のキャリアの中で私を大いに助けてくれました、そして私はこのプロジェクトを私の人生で最も重要なものの一つと考えています。