Cコンパイラ作成入門のメモ #6 (Step9)
はじめに
こちらをやってみたときのメモを書いていく。
今回はStep9
Commit
Step9
調べたこと、理解したこと
データ構造の流れ
イメージ図
メモ
参考
なし
NodeとCodegen実装の対応を理解
イメージ図
メモ
- gen()でどのようにパースした結果をアセンブリに変換しているのかを順を追って理解した。
参考
なし
スタックの伸びる方向について
イメージ図
メモ
- 「コラム: スタックの伸びる方向」に書いてあることがいまいちイメージできなかったので図にしてみた。
- 上位アドレス、下位アドレスで、上、下方向は図のようなイメージだと思う
参考
なし
consume_ident() の実装について
コード
// 次のトークンがidentのときには、トークンを1つ読み進めて // そのトークンを返す。それ以外の場合はNULLを返す。 static Token *consume_ident() { if (token->kind != TK_IDENT) return NULL;// 期待するトークンと不一致(変数でなければ)の場合はNULL // 次のトークンがIdentのときはreturnするtokenを退避 Token *ret = token; // tokenを次にconsumeする token = token->next; return ret; } // 新:primary = num | ident | "(" expr ")" static Node *primary() { // 次のトークンが"("なら、"(" expr ")"のはず if (consume("(")) { Node *node = expr(); expect(")"); return node; } Token *tok = consume_ident(); if (tok) { Node *node = calloc(1, sizeof(Node)); node->kind = ND_LVAR; //文字コードからaから何文字先かを求めてそれに8byte掛けて、オフセットを計算 node->offset = (tok->str[0] - 'a'+1) * 8; return node; } // ()でもidentでもなければ数値のはず return new_num(expect_number()); }
メモ
- 変数を読み進めるための関数。
- テキストには実装書いてないので、こんな感じに実装した。
- consume()とかを参考に、returnにはtokenを返すように実装
参考
なし
『for (int i = 0; code[i]; i++)』のcode[i]でloop終了条件にしていいの?
コード
// 9cc.c int main(int argc, char **argv) { //中略 // 先頭の式から順にコード生成 // program()でcode[i]の最後はNULLになっているはずなのでfor文の終了条件はcode[i]でOK for (int i = 0; code[i]; i++) { gen(code[i]); // 式の評価結果としてスタックに1つの値が残っている // はずなので、スタックが溢れないようにポップしておく printf(" pop rax\n"); } //中略 } //parse.c // program = stmt* void *program() { int i = 0; while (!at_eof()) code[i++] = stmt(); code[i] = NULL; //ここで末尾にNULLを入れているから, mainのコード生成のfor文の終了条件判定がcode[i]でOKなのか }
メモ
- なんで、for文の終了条件が
code[i]
でいいんだろう?と思った。 - ただ単に
program()
でperse結果をcode[]に格納するときに、末尾にNULL
を格納しているだけ。 - NULLポインタは評価すると
false
になるので、code[]終端でループ抜ける。
参考
- NULLポインタの定義についておさらいした
C言語 NULLポインタ【ポインタの参照を無効化する唯一の方法】
標準エラー出力でデバッグ
イメージ図/コード
// 新しいトークンを作成してcurに繋げる static Token *new_token(TokenKind kind, Token *cur, char *str, int len) { Token *tok = calloc(1, sizeof(Token)); tok->kind = kind; tok->str = str; tok->len = len; cur->next = tok; #ifdef DEBUG fprintf(stderr, "add new token. kind=> %d, str=> %s \n", kind ,str); #endif return tok; }
メモ
ビルドしたら、テスト通らなかったので、printfデバッグを試みた。
が、このコードは標準出力結果をアセンブラとして保存しているので、だめじゃんとなった。
標準エラー出力でデバッグすればよい。ということで、出力方法を調べた。
ちゃんとトークナイズはできてそう。ということはわかった。
ただ結局printfデバッグはあまり活躍せず、吐かれたアセンブリを読んでいって、pushとpopが逆に書いてあったり、いろいろケアレスミスを発見した。という感じで解決。
参考
参考にしたサイト
実装の参考
どう実装したらわからない部分参考にさせてもらった
ローカル変数導入 · pocari/compilerbook-9cc@9dbdc68 · GitHub
drawioファイルをブログに貼り付け
drawioをvscodeで描いて、githubのリンクを使ってブログで使う
Draw.ioをGitHub管理して画像を埋め込む - システム開発で思うところ
Draw.io Integrationの背景が暗いの嫌だなぁ
白背景にする方法
VScodeの拡張機能「Draw.io Integration」で背景色を白色に変更する方法
思ったこと
- 学習のススメ方について
- このステップは平日の仕事後の時間に15min.とかをコツコツ積み重ねながら進められたところがかなりよかった。
- まとまった時間はなくても進められる。と思えた。
- むしろ、よくわからないなぁと思いながら、中断して、次の日、仕事しながらバックグランドでそのことをぼんやり考えるので、ふとしたときに腹に落ちることが何度かあって、良かった。
- 細切れにやった方がよくわからないことを理解するときは、時間区切って、わからない状態で中断することがある意味効率的とも感じた
- draw.ioで図にかきながら理解したことが良かった