
【第2回】Brainfuckを実装しながら学ぶC++【実装してみよう!前編】
2021.12.20
第2回~Brainfuckを実装しながら学ぶC++
連載「Brainfuckを実装しながら学ぶC++」シリーズの第2回目です。
さて前回は、導入ということで、Brainfuck の仕組みを解説しました。
今回からは、実際に C/C++ を使って、実装していきましょう。
…と言いたいところですが、今回はまず C/C++ の基本的な話を中心に、解説していきたいと思います!
ですので、必要のない方は読み飛ばしていただいて OK です。
環境構築
これは、必要な人だけ読んでください。
今回必須な環境は、C/C++ コンパイラ「gcc」と、ビルドで使う「CMake」です。
「過去にインストールしたかもな…」と思った方は、以下のコマンドで確認してみてください。
1 2 | $ gcc --version $ cmake --version |
gcc/g++
gcc は、C/C++ コンパイラの一つです。
macOS であれば、Homebrew で簡単にインストールできます。
1 | $ brew install gcc |
ちなみに macOS では、デフォルトで「Clang」というコンパイラが入っているのですが、おそらくデフォルトのままでも大丈夫かと思います。
もし、筆者の環境と同じものにしたい場合は、Homebrew で新たに gcc を入れてください。
Windows では、MinGW というインストーラがよく利用されます。
CMake
CMake は、C/C++ ソースファイルを「解析・コンパイル」して、実行ファイルを生成するための Makefile を生成してくれるツールです。
macOS では、こちらも Homebrew でインストールできます。
1 | $ brew install cmake |
または、Windows 含め、以下からインストーラを入手することもできます。
【 CMake 公式サイト】
https://cmake.org/download/
C/C++プロジェクトを作る
プロジェクトディレクトリをつくるほどのものではありませんが、CMake を利用したいので作成します。
ちなみに、Jetbrains 社の C/C++ IDE である「CLion」では、同じようなプロジェクトが自動で生成されます。
もし、ライセンスを持っている方は、ぜひ試してみてくださいね!
さて本連載では、プロジェクト名を「brainfuck」としておきます。
まずは、以下のようなディレクトリ構成を作成しましょう。
1 2 3 4 | brainfuck/ ├── biuld/ # Cmakeによるビルド用ディレクトリ (ここに完成品が出来上がる) ├── CMakeLists.txt # CMakeスクリプト └── main.cpp # 実際にインタプリタを実装していくファイル |
それぞれのファイルに、以下のようなコードを書いてみてください。
※ CLion で自動生成されるものと同じです。
CMakeLists.txt
1 2 3 4 5 6 | cmake_minimum_required(VERSION 3.16) project(brainfuck) # プロジェクト名 set(CMAKE_CXX_STANDARD 11) # C++11 でのコンパイル add_executable(brainfuck main.cpp) # brainfuckという名前でビルド |
正直なところ、もっと古いバージョンでも実装可能ですが、今回は C++11 にしておきます。
main.cpp
1 2 3 4 5 6 | #include <iostream> int main() { std::cout << "Hello, World!" << std::endl; return 0; } |
こちらはよくある、最初の一歩「Hello, World!」です。
深く C++ を理解したいエンジニアのために、この基本的なコードを細かく解説していきます。
まず、 #include <iostream> は、入出力に関する標準ライブラリを読み込んでいる記述です。
メイン関数 int main(){ } では、Java におけるメインクラスのように、このスコープ内のコードが順に実行されていきます。
std:: は、標準ライブラリの名前空間 (namespace) です。
名前空間は、関数名やクラス名などに重複がないことがハッキリしていれば、以下のように省略ができます。
1 2 3 4 5 6 7 | #include <iostream> using namespace std; // stdは省略する、という宣言 int main() { cout << "Hello, World!" << endl; return 0; } |
ちなみに、上のコードの各意味は、次のとおりです。
- cout :出力ストリーム
- endl :改行
- << :C++ では演算子のひとつ
- cout :文字列や数値型を出力にスタックする
ビルドして実行してみる
CMake を利用したビルドと実行は、とても簡単。
1 2 3 4 5 | $ cd build # ビルド用ディレクトリに移動 $ cmake .. # CMakeLists.txtを探し解析する $ make # Makefileを実行 => brainfuckという名の実行ファイルが出来上がる $ ./brainfuck Hello, World! |
上手くいきましたね!
C/C++の基本要素
型と関数
C/C++ では、常に明示的に型を記述する必要があります。
これは関数も同様で、返すものの型を明記します。
もし、何も返すものがないのであれば、以下のように void を使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <iostream> #include <string> // 文字列型 using namespace std; void say_hello(string str){ // 本当はstd::string cout << "Hello, " + str + "!" << endl; } int main() { say_hello("Brainfuck"); // > Hello, Brainfuck! return 0; } |
他にも、よく使われる型として、以下のような型があります。
int | 整数型 (32bit = 4byte) |
float | 単精度浮動小数点型 (32bit = 4byte) |
double | 倍精度浮動小数点型 (64bit = 8byte) |
char | 文字型 (8bit = 1byte) ( string の構成要素であり整数も一応扱える) |
ポインタ
C/C++ の重要な要素、「ポインタ」の扱いにも、軽く触れておきます。
通常、変数は他の言語と同じく、値を指すものとして保持されます。
以下のようなコードは、違和感なく見ることができますよね?
1 2 | int a = 123; cout << a << endl; // > 123 |
ところが、値そのものではなく変数のアドレスを扱いたい場合には、「 *」と「 &」を使う必要があるのです。
例えば、以下のような具合です。
1 2 3 4 5 6 7 | int a = 123; // 通常の変数宣言 cout << a << endl; // > 123 int* a_ptr = &a; // aのメモリアドレスをポインタとして保持 cout << a_ptr << endl; // 例: > 0x7ffee6111b48 cout << (&a == a_ptr) << endl; // > 1 (true) cout << *a_ptr << endl; // > 123 |
型名に * を付ければ、その変数はメモリアドレスを持つ変数になります。
ちなみに、 int* a_ptr と int *a_ptr は同じ意味です。
また逆に、変数のメモリアドレスを参照する場合は、 &a と記述します。
このメモリアドレスを扱う、ということを理解するには、値渡しと参照渡しを比較してみるのが一番です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | int a = 123; // 通常の変数宣言 int b = a; // 値渡し int *c = &a; // 参照渡し cout << a << endl; // > 123 cout << b << endl; // > 123 cout << *c << endl; // > 123 b = 321; // b の値を変えてみる cout << a << endl; // > 123 cout << b << endl; // > 321 <= bだけ変わった cout << *c << endl; // > 123 *c = 456; // cの指す値を変えてみる cout << a << endl; // > 456 <= aのアドレスとcは一緒なので,aの値も代わる cout << b << endl; // > 321 cout << *c << endl; // > 456 <= cの指す値はもちろん変わる |
このように、単に値が同じであることと、メモリアドレスを共有するということの違いが分かるかと思います。
Python では、基本は全て参照渡しなので、最初は困惑するかもしれません。
関数の引数でも同じです。
例えば以下のように、関数の引数に & を明示的に記せば、変数名は違っても同じアドレスを指すものとして扱われるようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include <iostream> using namespace std; /** & をつけていないので「引数の値だけ使用する」ことを示す */ int func1(int num1, int num2){ num1 += num2; return num1; } /** & をつけることで明示的に「引数の値を変更する」ことを示す */ int func2(int& num1, int& num2){ num1 += num2; return num1; } int main() { int a = 3, b = 5; cout << func1(a, b) << endl; // > 8 cout << a << endl; // > 3 <= a は変更されない cout << func2(a, b) << endl; // > 8 cout << a << endl; // > 8 <= a も変更される return 0; } |
ポインタと配列
ここからは、さらに重要な話になります!
C/C++ における配列についてです。
通常 C/C++では、以下のように配列を定義できます。
1 2 | int arr[] = {1, 2, 3}; cout << arr[1] << endl; // > 2 |
ここは、特に問題ないでしょう。
しかし、以下のコードでは、どのように出力されるでしょうか?
1 2 | int arr[] = {1, 2, 3}; cout << arr << endl; // > ? |
試してみると分かりますが、 0x7ffeeebbeb3c のように、何かのメモリアドレスが出力されます。
これは、配列の先頭要素のアドレスです。
つまり、配列は一種のポインタであることが分かります。
さらに、ポインタですので、以下のように扱うことも可能です。
1 2 3 4 | int arr[] = {1, 2, 3}; cout << *arr << endl; // > 1 cout << *(arr+1) << endl; // > 2 cout << *(arr+2) << endl; // > 3 |
C/C++ のポインタは、かなり奥が深いですが、今回での基本的な話は以上になります。
その他に大切な要素は、インタプリタを実装しながら、確認していきましょう!
第3回目へつづく!
さて今回は、Brainfuck インタプリタを始める前に、大事な環境構築と C/C++ の基本の話をしました。
次回からは、いよいよ本格的な実装をしていきます!
その中で、C++ の大事な要素もバンバン出てきますので、これを機に C++ に入門していきましょう!
第3回はこちら!
第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世界初の量産型ポータブルコンピュータを開発したのに倒産!?アダム・オズボーン