
【第3回】Brainfuckを実装しながら学ぶC++【実装してみよう!中編】
2021.12.20
第3回~Brainfuckを実装しながら学ぶC++
今回で、「Brainfuckを実装しながら学ぶC++」シリーズの第3回目となりました。
ちなみに前回は、今回に備えて、C/C++ の基礎を解説していきましたね!
さて今回は、いよいよ本格的な BrainFuck の実装に入っていきます。
やや長丁場になりますが、一緒に頑張っていきましょう!
大枠の実装
前回に引き続き、「brainfuck」プロジェクトを使用します。
まずは、大枠を作っていきましょう!
命令の定義
連載の第1回目に解説したように、Brainfuck には、8つの命令が存在します。
それらを定数として、まずは定義していきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <iostream> using namespace std; // 命令をコンパイル時定数として定義 constexpr char INCREMENT = '+'; constexpr char DECREMENT = '-'; constexpr char RIGHT = '>'; constexpr char LEFT = '<'; constexpr char LOOP_START = '['; constexpr char LOOP_END = ']'; constexpr char OUTPUT = '.'; constexpr char INPUT = ','; int main() { return 0; } |
C++ 11 からサポートされる constexpr 修飾子は、以前まで使われていた #define INCREMENT '+' という、マクロ定義によるコンパイル時定数と同じ役割。
これらはその名の通り、コンパイル時に評価され展開されるため、実行時間の短縮につながります。
ちなみに C++11 以降でも、 #define で定数定義している人は多くいますが、公式では constexpr の使用を推奨しています。
各種変数の初期化
次に、以下を初期化するコードを書いていきましょう。
- メモリのサイズ
- ポインタの初期位置や値
- Brainfuck コード読み取りのポインタ
- Brainfuck コードの長さ
実際のコードは、次の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 | int main() { unsigned char memory[MEMORY_SIZE]; //! 1Byte=8bit (0~255) で構成されるメモリを定義 unsigned int ptr = 0; //! メモリポインタ (負の値は取らないのでunsigned) unsigned int code_ptr = 0; //! Brainfuckコードのポインタ unsigned int code_len = 0; //! Brainfuckコードの終端 (長さ) // メモリの0初期化 memset(memory, 0, sizeof(memory)); return 0; } |
memset() は、C 言語時代からあるもので、配列要素を一括初期化できる関数です。
ただ現在では、今回のような単純な配列の初期化などに、使われることが多くなりました。
ファイルの読み込み
今回作成しているインタプリタは、以下のような使い方を想定しています。
1 | $ brainfuck nanikashirano_code.bf |
ですので、「コマンドラインからの引数受け取り処理」と「そのファイル読み込み機構」を、ここで実装していきたいと思います。
コマンドライン引数の受け取り
C++ では、標準でコマンドライン引数を処理する機能があるので、とても簡単!
メイン関数に、以下のような引数を指定します。
1 2 3 | int main(int argc, char* argv[]) { // ... } |
argc は、自身の命令と、後続のコマンドライン引数を含んだ数値が格納されます。
argv 配列には、それらの文字列が格納されます。
例えば、 $ brainfuck samplecode.bf と実行すれば、
1 2 3 | > argc = 2 > argv[0] = "brainfuck" > argv[1] = "samplecode.bf" |
となります。
念のため、引数がなかった場合のエラー処理も書いておきましょう!
1 2 3 4 5 6 7 8 9 10 11 | int main(int argc, char* argv[]) { /* 省略 */ /* コマンドライン引数処理 */ if (argc < 2){ cerr << "Error: A Brainfuck code is not passed as a command-line argument." << endl; cerr << "Please pass an argument as the form, $ brainfuck [FILENAME]." << endl; return -1; } } |
cerr は、エラー出力用の出力ストリームです。
ファイル読み込み
次に、コマンドライン引数で受け取ったファイル名を元に、ファイルを開く処理を実装しましょう。
ファイルの扱いは、 #include <fstream> で使用可能になります。
ファイル読み込みは std::ifstream、書き込みは std::ofstream を使用します。
今回は、読み込みだけなので、以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <iostream> #include <fstream> // 追加 using namespace std; /* 省略 */ int main(int argc, char* argv[]) { /* 省略 */ ifstream file(argv[1]); //! ファイル読み込み if (!file){ // ファイル読みこみ失敗 cerr << "Error: The file, " << argv[1] << ", cannot be opened." << endl; return -1; } return 0; } |
ファイル処理
それでは、ファイルが上手く開けた場合の処理を考えます。
ここで取得したいのは、ファイルの中身 (スペース・改行含む) と、コードの長さ。
ファイル全体を一括で読み込みたいときは、 std::stringstream を利用することで、簡単に実現できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <iostream> #include <fstream> #include <string> //追加 #include <sstream> // 追加 using namespace std; /* 省略 */ int main(int argc, char* argv[]) { /* 省略 */ /* ファイルの中身を取得 */ stringstream buffer; //! stringstream用のバッファ buffer << file.rdbuf(); // ファイルをバッファとして取得 string code(buffer.str()); //! codeをstringとして取得 code_len = code.size(); //! コードサイズ // デバッグ用 cout << code_len << " Byte.\n" << code << endl; return 0; } |
ここでビルドディレクトリに、以下のような Brainfuck コードを追加して、動作確認をしましょう。
1 2 3 | +++++++++[->++++++++>+++++++++++>+++++<<<]>.>++. +++++++..+++.>-.------------.<++++++++.--------. +++.------.--------.>+. |
上のコードを、「hello.bf」として保存します。
そして、
1 2 3 4 5 6 7 | $ cd build $ make $ ./brainfuck hello.bf 121 Byte. +++++++++[->++++++++>+++++++++++>+++++<<<]>.>++. +++++++..+++.>-.------------.<++++++++.--------. +++.------.--------.>+. |
このように表示されれば、ファイル読み込みが上手く動作しています。
コード解析部の大枠作成
次に、核となる Brainfuck コード解析部分の大枠を作成して、今回は終わりにします。
基本は、命令を一つずつ読み込んでいくだけなので、以下のようなループが大枠です。
1 2 3 4 5 | /* Brainfuck 解析 */ while (code_ptr < code_len){ code_ptr++; } |
この while 文の中身で、その命令によって処理を分けていくのですが、ここではそれを switch 文を使用して実装していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /* Brainfuck 解析 */ while (code_ptr < code_len){ switch (code[code_ptr]) { case INCREMENT: break; case DECREMENT: break; case RIGHT: break; case LEFT: break; case LOOP_START: break; case LOOP_END: break; case OUTPUT: break; case INPUT: break; default: // 上記以外の命令はコメント扱い break; } code_ptr++; } |
まだ、各ケースは実装していないので何も起こりませんが、次回からはこの部分を実装していきます。
ここが完成すれば、Brainfuck インタプリタは出来上がりです!
第4回目へつづく!
今回は、インタプリタ実装に本格的に着手しました。
ただ、まだ「読み込んだコードをそのまま出力するだけ」という、実用性のないコードになっていますね…。
というわけで次回は、これを基に、どんどんコードを発展させていきましょう!
具体的には、インタプリタの核を実装していきます。
では、次回もお楽しみに!
第4回はこちら!
第1回目はこちら!
こちらの記事もオススメ!
書いた人はこんな人

- 「好きを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もり大歓迎!
また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。
以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit
ライトコードの日常12月 1, 2023ライトコードクエスト〜東京オフィス歴史編〜
ITエンタメ10月 13, 2023Netflixの成功はレコメンドエンジン?
ライトコードの日常8月 30, 2023退職者の最終出社日に密着してみた!
ITエンタメ8月 3, 2023世界初の量産型ポータブルコンピュータを開発したのに倒産!?アダム・オズボーン