Brainfuck Hello World实际如何运作?


118

有人将此邮件发送给我,并声称这是Brainfuck的世界(我希望如此...)

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

我知道它可以通过移动指针并递增和递减的东西来工作...

但是我仍然想知道,它实际上是如何工作的?首先如何在屏幕上打印任何内容?它如何编码文本?我一点都不明白...


31
必须很难维护以这种语言编写的应用程序..
e2-e4

17
@ ring0:不,那是只写的语言。
LetMeSOThat4U 2015年

有什么实际用途?
Yash Kumar Verma

10
@YashVerma它不需要一个。.–
Insane

49
@YashVerma在语言名称中明确指定。
Mateen Ulhaq '16

Answers:


255

1.基础

要了解Brainfuck,您必须想象0每个单元初始化的无限单元格数组。

...[0][0][0][0][0]...

当Brainfuck程序启动时,它指向任何单元。

...[0][0][*0*][0][0]...

如果向右>移动指针,则将指针从单元格X移动到单元格X + 1

...[0][0][0][*0*][0]...

如果增加单元格值,+则会得到:

...[0][0][0][*1*][0]...

如果再次增加单元格值,+则会得到:

...[0][0][0][*2*][0]...

如果减少单元格值,-则会得到:

...[0][0][0][*1*][0]...

如果向左<移动指针,则将指针从单元格X移动到单元格X-1

...[0][0][*0*][1][0]...

2.输入

要读取字符,请使用逗号,。它的作用是:从标准输入读取字符,并将其十进制ASCII码写入实际单元格。

看一下ASCII表。例如,!is的十进制代码33,而ais 97

好吧,假设您的BF程序存储器如下所示:

...[0][0][*0*][0][0]...

假设标准输入代表a,如果您使用逗号,运算符,则BF所做的是将a十进制ASCII码读取97到内存中:

...[0][0][*97*][0][0]...

您通常希望以这种方式思考,但是事实要复杂一些。事实是BF不会读取字符,而是读取一个字节(无论该字节是什么)。让我向您展示示例:

在Linux中

$ printf ł

印刷品:

ł

这是特定的波兰字符。此字符未通过ASCII编码进行编码。在这种情况下,它是UTF-8编码,因此它过去在计算机内存中占用一个以上的字节。我们可以通过进行十六进制转储来证明这一点:

$ printf ł | hd

这表明:

00000000  c5 82                                             |..|

零偏移。82是第一个c5字节,第二个字节代表ł(我们将按顺序读取它们)。|..|是图形表示,在这种情况下是不可能的。

好吧,如果您将ł输入作为传递给读取单个字节的BF程序的输入,程序存储器将如下所示:

...[0][0][*197*][0][0]...

为什么197呢 那么197十进制为c5十六进制。看起来很熟悉?当然。它是第一个字节ł

3.输出

要使用点打印字符,.它的作用是:假设我们将实际单元格值视为十进制ASCII码,则将相应字符打印到标准输出。

好吧,假设您的BF程序存储器如下所示:

...[0][0][*97*][0][0]...

如果现在使用点(。)运算符,则BF会执行以下打印:

一个

因为aASCII中的十进制代码是97

因此,例如这样的BF程序(97加2点):

++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++ ..

将其指向的单元格的值增加到97,并打印两次。

a

4.循环

在BF中,循环包括循环开始[和循环结束]。您可以认为这就像在C / C ++中,条件是实际单元格值。

看看下面的BF程序:

++[]

++ 将实际单元格值增加两次:

...[0][0][*2*][0][0]...

[]while(2) {},所以它的无限循环。

假设我们不希望这个循环是无限的。我们可以例如:

++[-]

因此,每次循环循环都会减少实际单元格值。一旦实际单元格值被0循环结束:

...[0][0][*2*][0][0]...        loop starts
...[0][0][*1*][0][0]...        after first iteration
...[0][0][*0*][0][0]...        after second iteration (loop ends)

让我们考虑另一个有限循环的例子:

++[>]

此示例显示,我们尚未在循环开始于的单元格处完成循环:

...[0][0][*2*][0][0]...        loop starts
...[0][0][2][*0*][0]...        after first iteration (loop ends)

但是,结束我们的起点是个好习惯。为什么呢 因为如果循环结束了它开始的另一个单元格,则我们无法假定单元格指针将位于何处。老实说,这种做法可以减少脑筋。


4
太酷了,现在我明白了:)
speeder

25
对于初学者尝试理解这种语言意识形态而言,这是一个完美的解决方案。恭喜,好帖子。
Casey 2014年

4
我见过的最佳Brainfuck简介。老实说,您可以通过发布信息来撤消BF

3
我猜想,如果您需要一个业余时间的项目,则可以随时向Brainfuck添加Unicode支持。
阿尔瓦罗·冈萨雷斯

3
发布后,BF不再是!BF!
thanos.a

52

Wikipedia有此代码的注释版本。

+++++ +++++             initialize counter (cell #0) to 10
[                       use loop to set the next four cells to 70/100/30/10
    > +++++ ++              add  7 to cell #1
    > +++++ +++++           add 10 to cell #2 
    > +++                   add  3 to cell #3
    > +                     add  1 to cell #4
    <<<< -                  decrement counter (cell #0)
]                   
> ++ .                  print 'H'
> + .                   print 'e'
+++++ ++ .              print 'l'
.                       print 'l'
+++ .                   print 'o'
> ++ .                  print ' '
<< +++++ +++++ +++++ .  print 'W'
> .                     print 'o'
+++ .                   print 'r'
----- - .               print 'l'
----- --- .             print 'd'
> + .                   print '!'
> .                     print '\n'

为了回答您的问题,,.字符用于I / O。文本为ASCII。

维基百科的文章接着在一些更深入,以及。

第一行a[0] = 10通过从0递增十倍来初始化。第二行的循环有效地设置了数组的初始值:(a[1] = 70接近72,字符'H'的ASCII码),a[2] = 100(接近101或'e' ),a[3] = 30(接近32个空格代码)和a[4] = 10(换行符)。该循环通过向单元格中添加7、10、3和1来实现a[1]a[2]a[3]a[4]分别各一次通过循环-在总共10个加法针对每个小区(给予 a[1]=70等)。循环完成后,a[0]为零。>++.然后将指针移到a[1],保持70,在其上加上两个(产生72,即为大写字母H的ASCII字符代码),并将其输出。

下一行将数组指针移至该数组指针并向a[2]其添加一个,产生101,即小写字母“ e”,然后将其输出。

由于“ l”恰好是“ e”之后的第七个字母,因此要输出“ ll”,还要再加上七个(+++++++),a[2]结果将输出两次。

'o'是'l'之后的第三个字母,因此a[2]再增加3次并输出结果。

程序的其余部分以相同的方式进行。对于空格和大写字母,将选择不同的数组单元,并根据需要递增或递减。


但是为什么要打印?或如何?这些评论向我解释了该生产线的意图,现在正在做什么。
加速

8
进行打印是因为编译器知道,.用于I / O,就像使用进行C打印一样putchar。它是由编译器处理的实现细节。

1
而且还因为它将“ Hello World”中ASCII字符的必需单元格设置为整数值
slugonamission

我希望有更深入的解释...但是:/
speeder

1
@speeder-我在答案中添加了来自Wikipedia的代码的深入解释。您可以查看链接的文章以获取更多信息。

9

为了回答如何知道要打印什么的问题,我在打印发生的代码的右侧添加了ASCII值的计算:

> just means move to the next cell
< just means move to the previous cell
+ and - are used for increment and decrement respectively. The value of the cell is updated when the increment/decrement happens

+++++ +++++             initialize counter (cell #0) to 10

[                       use loop to set the next four cells to 70/100/30/10

> +++++ ++              add  7 to cell #1

> +++++ +++++           add 10 to cell #2 

> +++                   add  3 to cell #3

> +                     add  1 to cell #4

<<<< -                  decrement counter (cell #0)

]            

> ++ .                  print 'H' (ascii: 70+2 = 72) //70 is value in current cell. The two +s increment the value of the current cell by 2

> + .                   print 'e' (ascii: 100+1 = 101)

+++++ ++ .              print 'l' (ascii: 101+7 = 108)

.                       print 'l' dot prints same thing again

+++ .                   print 'o' (ascii: 108+3 = 111)

> ++ .                  print ' ' (ascii: 30+2 = 32)

<< +++++ +++++ +++++ .  print 'W' (ascii: 72+15 = 87)

> .                     print 'o' (ascii: 111)

+++ .                   print 'r' (ascii: 111+3 = 114)

----- - .               print 'l' (ascii: 114-6 = 108)

----- --- .             print 'd' (ascii: 108-8 = 100)

> + .                   print '!' (ascii: 32+1 = 33)

> .                     print '\n'(ascii: 10)

9

Brainfuck 和它的名字一样。它仅使用8个字符> [ . ] , - +,是学习最快的编程语言最难实现和理解。 …。最后使您最终陷入瘫痪。

它将值存储在数组中:[72] [101] [108] [111]

让,最初指向数组的单元格1的指针:

  1. > 向右移动指针1

  2. < 向左移动指针1

  3. + 将单元格的值增加1

  4. - 将element的值增加1

  5. . 当前单元格的打印值。

  6. , 输入当前单元格。

  7. [ ] 循环,+++ [-]的3个计数器对bcz进行计数,它前面有3个'+',并且-将count变量减1。

存储在单元格中的值是ascii值:

所以参考上面的数组:[72] [101] [108] [108] [111]如果您匹配ascii值,您会发现它是Hello写法

恭喜!您已经了解了BF的语法

--- 更多的东西 ---

让我们编写第一个程序,即Hello World,之后您就可以用这种语言写下您的名字了。

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

分为几部分:

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

制作一个由4个单元格组成的数组(数量>),并设置一个10的计数器,例如:— —伪代码—

array =[7,10,3,1]
i=10
while i>0:
 element +=element
 i-=1

因为计数器值存储在单元格0中,并且>移至单元格1将其值更新+7>移至单元格2将其先前值递增10,依此类推。

<<< 返回单元格0并将其值减1

因此,在循环完成之后,我们得到了数组:[70,100,30,10]

>++. 

移动到第一个元素并将其值增加2(两个'+'),然后使用该ascii值打印('。')字符。例如在python中:chr(70 + 2)#打印'H'

>+.

移动到第二个单元格增量1到其值100 + 1并打印('。')其值,即chr(101)chr(101)#打印'e'现在下一个中没有>或<因此它取现值的最新元素并仅递增

+++++ ++..

最新元素= 101,因此101 + 7并打印两次(因为有两个'..')chr(108)#prints l可以用作两次

for i in array:
    for j in range(i.count(‘.’)):
           print_value

——— 它在哪里使用?——-

它只是一种挑战程序员的玩笑语言,几乎没有在任何地方使用。


4

所有答案都是彻底的,但它们缺少一个微小的细节:打印。在构建您的Brainfuck转换器时,您还要考虑字符.,这实际上是Brainfuck中的打印语句。因此,您的大脑翻译器应该做的是,每遇到一个.字符,便打印当前指向的字节。

例:

假设您有-> char *ptr = [0] [0] [0] [97] [0]...如果这是一个让人难以理解的声明: >>>.您的指针应向右移3个空格:[97],所以现在*ptr = 97,在您的翻译器遇到a之后.,它应该调用

write(1, ptr, 1)

或任何等效的打印语句以打印当前指向的字节,其值为97a然后将字母打印在上std_output


1

我认为您要问的是Brainfuck如何知道如何处理所有代码。有一个用Python等高级语言编写的解析器,用于解释点的含义或代码中加号的含义。

因此,解析器将逐行读取您的代码,并说好,那里有一个>符号,所以我必须提前存储位置,代码就是,如果(该存储位置中的内容)==>,内存定位= +内存定位,即用高级语言编写,类似地,如果(存储位置的内容)==“。”,则打印(存储位置的内容)。

希望这可以解决。tc

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.