mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
1654 字
8 分钟
浅谈 Brainfuck
2025-10-12
统计加载中...
TIP

Brainfuck 这个名字真的是一点都没起错,我写的时候感觉我的 Brain 正在被 Fuck。

目录#

1. Brainfuck 介绍#

Brainfuck 是一种极小化的编程语言,由 Urban Müller 在 1993 年设计。它的特点非常独特:整个语言只有 8 个操作符,但却是 图灵完备(Turing Complete) 的,也就是说,它理论上可以完成任何计算任务。

1.1. Brainfuck 的 8 个操作符#

Brainfuck 假设存在一个无限长的数组(实践中通常选择长度为 30000 的 uint8 数组),以及一个指向数据的指针(实践中初始状态指针通常指向第一个内存地址),然后通过 8 个操作符来操作这个数据。

  • .:输出当前单元的 ASCII 值
  • ,:从标准输入读取一个字符,并将其存储到当前单元
  • +:将当前单元的值加一
  • -:将当前单元的值减一
  • >:将数据指针向右移动一个单元
  • <:将数据指针向左移动一个单元
  • [:如果当前单元的值为零,则跳转到与之匹配的 ] 后面的第一个字符
  • ]:如果当前单元的值不为零,则跳转到与之匹配的 [ 前面的第一个字符

1.2. Brainfuck 与图灵机#

Brainfuck 的假设和操作符设计,很明显是受到了图灵机的启发。图灵机是一种理论计算机模型,由一个无限长的纸带、一个读写头和一个状态寄存器组成。Brainfuck 的数据、指针和操作符,都可以看作是图灵机的对应部分:

  1. 图灵机的无限长纸带,可以看作是 Brainfuck 的无限长的数组。
  2. 图灵机的读写头,可以看作是 Brainfuck 的数据指针。
  3. 图灵机的读写操作,可以看作是 Brainfuck+-., 操作符。
  4. 图灵机读写头的移动,可以看作是 Brainfuck>< 操作符。
  5. 图灵机的状态寄存器与控制规则,可以看作是 Brainfuck[] 操作符,它们提供了条件跳转的功能。

不难从直觉上感觉到 Brainfuck 是图灵完备的。

1.3. 几个例子#

我先给出不带注释的版本,然后再给出有注释的版本。看到一坨代码不用害怕,仔细看注释,你会发现它其实很简单。

值得注意的是,因为 Brainfuck 只识别这几种字符,任意其他字符都会被忽略,所以你几乎可以用你习惯的任何语言的注释风格写注释,都可以作为合法的 Brainfuck 代码。(除了 HTMLMarkdown 的注释)

1.3.1. 通过循环实现 uint8 乘法#

注意:输入输出都是以 ASCII 码的形式进行的,比如输入 1 实际上输入的是 49。

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

有注释的版本:

, # 在第 0 个位置输入乘数 A
>[-]>[-]>, # 在第 3 个位置输入乘数 B,顺便清空第 1、2 个位置
[<+<+>>-] # 把 B 复制到第 1、2 个位置
<<< # 指针回到第 0 个位置
[ # 以 A 为循环计数器
>> # 指针移动到第 2 个位置
[<+>>+<-] # 循环,在第 1 和 3 个位置加 B
> # 指针移动到第 3 个位置
[<+>-] # 恢复第 2 个位置的值
<<<- # 循环计数器减 1
]
>. # 回到第 1 个位置输出 A*B

1.3.2. Hello World#

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

有注释的版本(这里用加代替+是为了防止不小心被识别成关键字):

[-] # 清空第 0 个位置
>[-]>[-]>[-]>[-] # 清空第 1、2、3、4 个位置
<<<< # 指针归位
++++++++++ # 把第 0 个位置的值设为 10
[ # 把第 0 个位置作为循环计数器
>+++++++ # 第 1 个位置:10*7=70
>++++++++++ # 第 2 个位置:10*10=100
>+++ # 第 3 个位置:10*3=30
>+ # 第 4 个位置:10*1=10
<<<<- # 循环计数器减 1
]
>++. # 输出 70加2=72(H)
>+. # 输出 100加1=101(e)
+++++++.. # 输出 101加7=108(l)两次
+++. # 输出 108加3=111(o)
>++. # 输出 30加2=32(空格)
<<+++++++++++++++. #输出 72加15=87(W)
>. # 输出 111(o)
+++. # 输出 111加3=114(r)
------. # 输出 114减6=108(l)
--------. # 输出 108减8=100(d)
>+. # 输出 32加1=33(!)
>. # 输出 10(换行)

2. Brainfuck 编译器#

听到 Brainfuck 的操作如此简单,相信很多人能几十行代码量写出一个 Brainfuck 解释器。或者一个没有优化而是直接模拟进行操作的编译器。

但是由于 Brainfuck 本身的特性,一旦对于稍微复杂的逻辑,解释运行的效率就会变的极其低下,而且,为了避免在代码里写上 100 个 +,通常会用循环来实现,那效率就更低了。正所谓“工欲善其事,必先利其器”,所以,我写了一个 Brainfuck 编译器,把 Brainfuck 代码直接编译成二进制文件,其中会经过循环展开、常量传播、死代码消除等优化,以提升运行效率。

整体解释器的实现通过 Pythonllvmlite 库实现。目前实现了一个基本的优化,但是由于我还没来得及学习编译原理,所以代码质量可能不够高,因此我可能需要对代码进一步优化后才会公开。

2.1. 效果展示#

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

这是一个利用三重循环实现的 Brainfuck 编译器,功能是输出 H,编译过程中生成的中间表示为:

; ModuleID = "brainfuck_compiler"
target triple = "unknown-unknown-unknown"
target datalayout = ""
define i32 @"main"()
{
entry:
%"memory" = alloca [30000 x i8]
%".2" = getelementptr [30000 x i8], [30000 x i8]* %"memory", i32 0, i32 0
%"ptr" = alloca i8*
store i8* %".2", i8** %"ptr"
call void @"llvm.memset.p0i8.i32"(i8* %".2", i8 0, i32 30000, i1 0)
%".5" = load i8*, i8** %"ptr"
%".6" = getelementptr i8, i8* %".5", i32 1
store i8* %".6", i8** %"ptr"
%".8" = load i8*, i8** %"ptr"
%".9" = trunc i32 72 to i8
store i8 %".9", i8* %".8"
%".11" = load i8*, i8** %"ptr"
%".12" = getelementptr i8, i8* %".11", i32 1
store i8* %".12", i8** %"ptr"
%".14" = load i8*, i8** %"ptr"
%".15" = trunc i32 0 to i8
store i8 %".15", i8* %".14"
%".17" = load i8*, i8** %"ptr"
%".18" = getelementptr i8, i8* %".17", i32 -2
store i8* %".18", i8** %"ptr"
%".20" = load i8*, i8** %"ptr"
%".21" = trunc i32 0 to i8
store i8 %".21", i8* %".20"
%".23" = load i8*, i8** %"ptr"
%".24" = getelementptr i8, i8* %".23", i32 1
store i8* %".24", i8** %"ptr"
%".26" = load i8*, i8** %"ptr"
%".27" = load i8, i8* %".26"
%".28" = zext i8 %".27" to i32
%".29" = call i32 @"putchar"(i32 %".28")
ret i32 0
}
declare i32 @"putchar"(i32 %".1")
declare i32 @"getchar"()
declare void @"llvm.memset.p0i8.i32"(i8* %".1", i8 %".2", i32 %".3", i1 %".4")

可以看出:三重循环直接被展开成了常量,当然,LLVM 本身对中间表示还有进一步的优化,它的优化能力十分强劲。

IMPORTANT

未完待续…

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

浅谈 Brainfuck
https://milk2715093695.github.io/posts/brainfuck/
作者
Milk
发布于
2025-10-12
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00