RustでCコンパイラ その08 STEP16まで(単項&,*の導入)
2026-04-09 06:08:44
- Tags:
- Rust
下記をRustで実装する続き。
Githubは下記。
commitコメントに、learnt step N って書いているから、該当のところをみれば変更が分かると思う。
少し間があいてしまったけど、STEP16
この章からポインタと文字列を扱うためにほぼ最後まで続く。
そのため、今回はポインタからアドレスを取り出す&と、そのアドレスの中身にアクセスする*の実装
実装自体は難しくないかなって思ってたけど、
'{ x=3; y=5; return *(&x+8); }'
というテストが通らなくて詰む。(答えはyの値である3)
どうも毎回おかしい値が出ているので、なんかメモリ関連がバグっているというのは分かる。
ということで、前回 作った、自作(Claude Codeまかせだけど) のデバッガーの出番。
こんな感じで表示される。

このテストのプログラムではないけど、左が自作コンパイラで出力したアセンブリ、右がそれをSTEP実行している途中。
レジスタやスタックが毎回更新されるのめっちゃ便利だけど、 よく考えたら、デバッガーとしては普通の機能。
まあ、たぶん世の中のプログラマーはgefやpwndbg使っているだろうから、こんな奇特な環境でやっているのは数少ないだろうけど。
閑話休題。
ステップ実行しつつ、自作コンパイラをもう一度見返してみて考えてみたのだが、
プログラムは合っているっぽいんだよね。
というのも、参照実装の今回のSTEPのコミットを見てみると、
Add unary & and * · rui314/chibicc@863e2b8
確かに、
今回のテストケースやその次のテストケースは、
assert 5 '{ x=3; y=5; return *(&x+8); }'
assert 3 '{ x=3; y=5; return *(&y-8); }'
となっていて、
変数xの+8のアドレス位置にyがあるという前提になっている。
これはつまり、先に宣言された変数が低アドレスになり、
後に宣言された変数が高アドレスになるということ。
でも、本書は元々メモリ配置は高アドレス方向から低アドレス方向、
つまりアドレスの値は、だんだんと小さい値になっていくはずなんだよね。
疑問に思って実際のCコンパイラとか世の中のx86_64のプログラムはどうなのだろうって思ったけど、
基本的に僕と同じ方向にメモリ配置しているから、
そうなると、xの+8にyではなく、-8にyとなる。
とは言っても、実際のCコンパイラは、型があるし、ポインタアクセスも同じ配列でしかできないはずなので、
今回のように何の関連もない変数を移動できるってことはない。
さらに、+8みたいなメモリサイズを気にしないといけないC言語なんて存在しない(はず)で、
+1みたいに、1つ分ずらす的なCプログラムを書けば、裏で、自動で型に合ったメモリサイズ1つ分を計算してくれて、その結果隣の変数にアクセスできるようになる。
なので、今回のプログラムは、まだ実際のC言語には似ても似つかない段階でしかない。
ということで、参照実装が上手くいっている理由は掴めないものの、
今の段階で深追いしても仕方ないという結論を出した。
ということで、テストケースも、
assert 5 '{ x=3; y=5; return *(&x-8); }'
assert 3 '{ x=3; y=5; return *(&y+8); }'
という風にアドレスのたどり方を逆に変えてOKとした。
ふぃー。
だんだんアセンブリに慣れて、読むのが速くなってきた。
次はSTEP17。いよいよintというキーワードが登場。

