最爱小鸭吧 关注:22贴子:2,418

Brainfuck入门

只看楼主收藏回复

Brainfuck是一种知名的esolang。本贴作为Brainfuck的入门教程,将对Brainfuck做一个简单的讲解,并最终实现一个简单的Brainfuck quine。


IP属地:浙江1楼2023-02-11 20:16回复
    机器模型
    Brainfuck的指令与内存分离。
    指令部分,Brainfuck机读入指令并执行。当执行完最后一条指令时,Brainfuck停机。
    内存部分,Brainfuck机有一条无限长的纸带,其中纸带又由无限多个单元构成。单元是Brainfuck的最小存储单位。同时还有一个指针,指向一个单元。指针可以向左或向右移动。


    IP属地:浙江2楼2023-02-11 20:19
    回复
      指令 含义
      > 将指针向右移动一个单元
      < 将指针向左移动一个单元
      + 指针指向的单元的值加一
      - 指针指向的单元的值减一
      . 以ASCII的形式输出指针指向的单元的值
      , 以ASCII的形式输入值到指针指向的单元
      [ 如果指针指向的单元的值为0则跳转到匹配的 ] 的后一指令
      ] 如果指针指向的单元的值不为0则跳转到匹配的 [ 的前一指令


      IP属地:浙江3楼2023-02-11 20:22
      回复
        未定义行为及约定
        单元的大小:我们约定单元的大小为8bits,并在讲解时将其认为是无符号类型的。
        数组的大小:我们在讲解时不在意大小,认为其是向两端无限延伸的。
        换行:可能读到\r\n或\n,但我们始终用\n表示换行。
        EOF:我们约定EOF的值为-1(255),并在讲解时始终用EOF来表示其值。


        IP属地:浙江4楼2023-02-11 20:59
        回复
          Brainfuck解释器
          char i[8192],*I=i,d[8192],*D=d,*s[256],**S=s,t,c;main(){for(gets(i);t=*I;I++)t-43?t==44?*D=getch():t-45?t-46?t-60?t-62?t-91?t-93?:*D?I=*S:--S:*D?*++S=I:({c=1;do t=*++I,t-91?t-93?:--c:++c;while(c);}):++D:--D:putchar(*D):--*D:++*D;}
          需要注意的是,此解释器与我们上述的约定并不相符,请自行斟酌使用。


          IP属地:浙江5楼2023-02-11 21:27
          回复
            表示方法约定
            我们将使用“结构-代码-注释”的方式来表示一个算法。没有特别指出时,结构表示了该算法的初始状态。
            结构:我们用[]来表示算法的入口点,用{}来表示算法的出口点
            代码:用In表示重复执行I指令n次,用(S)n表示重复执行S代码段n次
            代码:如果当前单元到单元A可定位,则用>A表示从当前单元移动至单元A
            其余约定将在楼中楼补充。


            IP属地:浙江6楼2023-02-22 15:33
            回复
              接下来讲解一些相当容易的基本结构


              IP属地:浙江来自Android客户端7楼2023-02-22 15:50
              回复
                1)已经加精。


                IP属地:加拿大来自iPhone客户端8楼2023-02-25 17:16
                回复
                  2)太好玩太疯狂太滕维建


                  IP属地:江苏来自Android客户端9楼2023-03-11 23:10
                  回复
                    不错


                    IP属地:河北11楼2023-08-19 13:05
                    回复
                      从此处起,我们将更换描述语言。
                      我们通常用一个字母来命名一个单元,不区分大小写。通常情况下,我们用大写。若要强调其值为0,则用小写。若要强调其值在前后发生了变化,则加上上标'。若要强调其值,则写在行末的注释里。
                      我们将用*表示当前单元。如果当前单元对目标单元可定位,用*~>d或~>d表示将指针从当前单元移动至目标单元d。我们用如下格式来描述一个模块,并均用[]来表示入口点和出口点。
                      模块名
                      初始情况
                      若干终止情况
                      代码
                      解释
                      譬如,上节的习题,其中乘法可写为:
                      MUL(A,B,C) ;C=A*B
                      [A] B t c
                      [a] B t C
                      [->[->+>+<<]>[-<+>]<<]
                      A作为计数器,每次复制B到C(上节习题,转移和复制实质上是加)。


                      IP属地:浙江12楼2023-12-04 15:20
                      回复
                        本来是想讲一下“标志”的概念,但是感觉没什么必要,就直接跳过好了。
                        我们知道,要想区分不同情况,就必须要用[],因为这对指令是bf中唯一具有逻辑判断功能的指令。而[]的跳出条件为指向单元的值为0,那么用值的不同来区分情况便显得有些困难了。因此,我们不妨转换一下思路,改用出口点位置的不同来区分情况。这可能和我们用常规语言编程时的思路大不一致,但确实是一种可行的解决方案。本文将相当多地采用这种设计思路,请确保你能够理解这种思路。
                        譬如,我们需要比较两个数的大小。很明显我们需要双重的[]。我们通过设计不同的出口点来区分A和B的大小情况。
                        CMP(A,B)
                        [A] 0 B
                        [a] 0 B' ;a <= b
                        A'[0] b ;a > b
                        [->>[-<]<]
                        这样,我们在不同的出口点跳出。现在,我们可以利用位置的不同,合适地设置一些标志来实现一些功能,最后将两种情况的指针均指向同一处来方便后续处理(称之为“同步”)。至于怎么设置标志,我们放在if-else节进行说明。


                        IP属地:浙江13楼2023-12-04 15:23
                        回复
                          if-else实在太简单了,无非是两个标志和两个非嵌套的[],没什么可说的


                          IP属地:浙江来自Android客户端14楼2024-03-08 23:36
                          回复
                            好了,现在你已经学会brainfuck了,快来写个quine试试吧


                            IP属地:浙江来自Android客户端15楼2024-03-08 23:38
                            回复