- 低レイヤを知りたい人のためのCコンパイラ作成入門 https://www.sigbus.info/compilerbook
- 困ったら見るリファレンス実装: https://github.com/rui314/chibicc
- 自分で写経(?)しているコード https://github.com/sbwhitecap/9cc
記録を取ることもなく黙々とやっていたので、日記はステップ11から。
- 2020/6/11
- 無引数の定義を実装した。
- 2020/5/28
- 忙しかったのと、ほかにやってみたいことがあったのでブランクが空いてしまった。
- 関数呼び出しの実装をした。
- strndup関数がimplicit declaration errorになって呼べないのでおかしいなと思って、manを見たりStackoverflowを見た。原因は、プログラム内で_POSIX_C_SOURCEを定義してないせいで関数原型宣言が公開されていなかったのだった。
- 2020/5/12
- for文を追加した。
- ブランク空けちゃったのはもったいない。
- リファレンス実装では補助的なファクトリ関数を用意して読みやすさと書きやすさを高めている。
- new_unaryやread_expr_stmtなど。
- リファレンンス実装ではlhsを使いまわしているのだから、自分は二項演算子用のファクトリ関数で書いているが、本来はunary用専用のノードを用意したほうがいいのだろうなと思った。
- これはTODOとしたい。
- 2020/5/7
- if文とwhile文を追加した。
- リファレンス実装の中でシリアルナンバーを一時的な変数に保存しているのを見て、どうしてこう書いているのか不思議に思い、そうしないで直接シリアルナンバーを出力するよう書いてみたところ、このテストケースでGNUアセンブラからラベルが見つからないエラーが出た。
- 理由は単純で、子のノードをコンパイル中にシリアルナンバーがインクリメントされてしまい、戻ってきたら対応が取れなくなってしまうからだった。
- 2020/5/5
- リファレンス実装に倣ってFunction構造体を足してみた。
- 9ccはchibiccを参考してCでやっているが、zinccでのやりかたも気になりCommon Lispで真似てみることにした。CLだとテストを整備するのが少々煩雑で、Cとシェルスクリプトの組みあわせのやりかたはとても単純で良いと思った。
- 2020/5/4
- 2020/4/28
- ステップ9からステップ11まで進んだ。
- 可変長の変数を定義できるようになった。
- 本では、識別子に使用可能な文字を判定するのにctype.hのisalnum(3)関数の代わりにis_alnum関数を定義して使っていた。isalnum(3)はアンダースコアを認識せず、ロケールに左右されるためだろうか?
- returnキーワードを導入した。
- gdbのTUIを起動する方法を知った。
Tips/気をつけること
連結リストを構築するときは、ダミーのhead要素を作ってそこに新しい要素を繋げていって、最後にhead->nextを返すようにするとコードが簡単になります。このような方法ではhead要素に割り当てられたメモリはほとんど無駄になりますが、ローカル変数をアロケートするコストはほぼゼロなので、特に気にする必要はありません。
/* こういうこと? */ #include <stdio.h> #include <stdlib.h> typedef struct Cell Cell; struct Cell { struct Cell *next; char *word; }; /* producer */ Cell *create_list(void) { Cell dummy = { .next = NULL, .word = NULL }; Cell *current = &dummy; char *words[] = {"akizuki", "teruzuki", "suzutsuki", "hatsuzuki", NULL}; for (char **place = words; *place != NULL; place++) { Cell *cell = calloc(1, sizeof(Cell)); cell->word = *place; cell->next = NULL; current->next = cell; current = cell; } return dummy.next; } /* consumer */ int main(void) { Cell *current = create_list(); for (; current != NULL; current = current->next) { printf("%s\n", current->word); } return 0; }
前章までに実装した機能では、関数の最後に必ず1つのret命令を出力していました。この章で説明した方法でreturn文を実装すると、return文ごとにさらに余分なret命令が出力されることになります。それらの命令はまとめることも可能ですが、ここでは簡単に実装するために、ret命令を複数出力してもかまわないということにしました。こういう細かいところは今の時点では気にしても仕方がないので、実装のシンプルさを優先することが大切です。難しいコードを書けるというのは役立つスキルですが、そもそもコードを難しくしすぎないというのは、時にはさらに役立つスキルなのです。
- 無駄が気になることは多い。つい、もっと賢い処理にしようとしてしまうこともあるが、その努力に見合わない結果になるどころか、複雑な書き方になりやすいってことだろうか?
Undefined Behavior(未定義の動作)