飛行物体の実験室

brainf*ck完全に理解した(Hello world)

アイキャッチ
2024-05-13

早速ですが、皆さんはbrainfuckというプログラミング言語をご存じでしょうか。

どんなコードなのか見てみましょう。以下がhello worldを出力するコードです。

++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.

パッと見何をしているかわからないコードですが、今回の目的はこのコードを紐解き、理解することにあります。早速やっていきましょう。

brainfuckに用いることができる指示は全部で8つです。

"+", "-", ">", "<", "[", "]", ".", ","

これだけです。アセンブラより少ないです。

https://qiita.com/TomoShiozawa/items/25dcce1540085df71053

こちらの記事で色々と説明してくれています。

定義として読むのはいいのですが、これを読んだだけではいまいち挙動が理解できませんでした。

なのでざっくりこの8つの記号と基本的なルールを説明してしまいます。(ちゃんとした定義ではないので注意)

ウェブブラウザで動くBrainfuckというサイトがあるので、動作をすぐに試すことができます。(なんとスマホからも動く!)

画面を見るとResult, Memory, Code, CodeEditorという表記があります。Resultは出力欄、Memoryは現在保存されている値の一覧と現在(ポインタ)位置を示しています。CodeはCodeEditorに書いたコードが表示されます。

具体的な指示を見てみましょう。

記号

説明

+

ポインタが指す場所にある値を+1

-

ポインタが指す場所にある値を-1

>

ポインタが指す場所を一つ右にずらす

<

ポインタが指す場所を一つ左にずらす

[

ポインタが指す場所にある値が0なら、コードの対応する]のところまでジャンプ

]

ポインタが指す場所にある値が0でないなら、コードの対応する[のところまでジャンプ

.

ポインタが指す場所にある値をASCIIコードで出力

,

入力を受け、ポインタが指す場所に入力値を代入

実際に動きとしては見ると、チューリングマシンと似ています。

では一つ一つやっていることを紐解いて行きます。

Hello Worldのコードは横並びで書かれていますが、これがわかりにくさに拍車をかけています。

なので、インデントを用いて書き直します。

++++++++
[
  >++++
  [
    >++
    >+++
    >+++
    >+
    <<<<-
  ]
  >+
  >+
  >-
  >>+
  [<]
  <-
]
>>.
>---.
+++++++.
.
+++.
>>.
<-.
<.
+++.
------.
--------.
>>+.
>++.

こんなところでしょうか。brainfuckにおいて、この8つの記号以外の内容は完全に無視されるので、この中にいろいろ書き込みつつ読み解くこともできます。

まず最初の

++++++++

です。+が8つなのでアドレス0に8を代入したことになります。

ここで分かりやすく記述する為に現在のメモリ状態を示す記法を独自に定義してしまいます。

現状態を(*8)とします。

これはすなわちアドレス0部分に8が入っており、ポインタ(*)はアドレス0に合っているということです。

次に進みます。

[が登場しました。

ここで現在位置の数値を見ると8なので、処理は行われず次の行へ進みます。

>++++

この指示は、ポインタが指す場所を一つ右にずらして4を加算ということになります。

この行が終わったときの状態はこうなります。

(8, *4)

また[ですが数値は4なので処理が次行に回ります。

>++
>+++
>+++
>+

次は4行一気に見てみましょう。

この後の結果はこうなります。

(8, 4, 2, 3, 3, *1)

問題になるのは次の行です。

<<<<-

結果は

(8, *3, 2, 3, 3, 1)

です。その後に]が来ています。今見ている数値は3なので、コードがジャンプして4行目の[に戻りました。

この後

[
  >++
  >+++
  >+++
  >+
  <<<<-
]

が実行され続けます。

この中の処理は、アドレス1の数値が0になるまで続きます。

処理は

>++++
[
  >++
  >+++
  >+++
  >+
  <<<<-
]

で1セットになります。

これはアドレス1が0になるまで(=4回)

アドレス2に2

アドレス3に3

アドレス4に3

アドレス5に1

を足す事になり、処理が次に進むタイミングでの結果はこうなります。

(8, *0, 8, 12, 12, 4)

このコードは掛け算をしていたわけです。

故に3〜10行のコードは

>
>++++++++
>++++++++++++
>++++++++++++
>++++
<<<<

と同じ意味になります。

ここでコードの前半を見返すと

++++++++
[
  >++++
  [
    >++
    >+++
    >+++
    >+
    <<<<-
  ]
  >+
  >+
  >-
  >>+
  [<]
  <-
]

となっています。

内側の中括弧まで内容を見ました。

外側の中括弧もやっていることは似ています。

アドレス0の値が0になるまで(=8回)中括弧の中身を実行しているのです。

>+
>+
>-
>>+
[<]
<-

この処理がやっているのは、

アドレス2に+1、アドレス3に+1、アドレス4に-1アドレス、アドレス6に+1して

アドレス1(0が入っているアドレス)へ戻る

アドレス0に-1

なので

(8, *0, 8, 12, 12, 4)

の状態から

(*7, 0, 8+1, 12+1, 12-1, 4, +1)

となりました。これをあと7回くり返すということは、ここまででやっていた処理というのは

(*0, 0, 9×8, 13×8, 11×8, 4×8, 8)

つまり

(*0, 0, 72, 104, 88, 32, 8)

を得る行為だったのです。

この数値は何なのかというと、ASCIIコードを見ると意図が見えてきます。

72は"H"、104は"h"、88は"X"、32は" "となります。

大文字・小文字・スペースの準備をしていたわけです。これがあると、後の作業が楽になるのです。

それでは出力欄を見ていきます。

>>.
>---.
+++++++.
.
+++.
>>.
<-.
<.
+++.
------.
--------.
>>+.
>++.

ここからは後戻り無しの出力です。

早速、72を出力し、Hが出ます。

隣に移り、104-3で101、すなわちeを出力。

8足してlを2回出力、3足してo、スペースを出力、W、o、r、l、d、!と来て最後に改行を出力して終わりです。

と、言うわけでコードの理解ができました。

Hello World!を出力するだけなら、こんな掛け算をたくさんする必要はなく+だけで書けばよいはずです。このコードはbrainfuckをより難しく感じさせる意図を持ったものだと考えられます。

実際の中身を紐解いてみるとそこまで難解な事をやっているわけではないのです。

他にもbrainfuckの構文を変形させたCow言語やOok!言語がありますがこれも意図は同じでしょう。

そもそも難解プログラミング言語の意図がそこにあるので言及する意味はありませんが、その特徴はコードにも出るということなのでしょう。

今回はbrainfuckのHello World!を紐解いてみました。難解言語ではありますが、「計算機械」を動かず言語としては興味深いものでした。

飛行物体の実験室ではこういった雑談的な内容も取り入れていきますので、今後ともよろしくお願いします。

ではまたお会いしましょう。