138

有人把这个发给我,并声称这是 Brainfuck 中的一个你好世界(我希望如此......)

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

我知道它通过移动指针以及递增和递减的东西来工作的基础知识......

但是我仍然想知道,它实际上是如何工作的?它首先如何在屏幕上打印任何内容?它如何对文本进行编码?我完全不明白……

4

6 回答 6

280

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 表。例如,十进制代码!33a而是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. 输出

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

好吧,让我们想象一下您的 BF 程序内存如下所示:

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

如果您现在使用点 (.) 运算符,BF 所做的是打印:

一个

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

所以例如像这样的BF程序(97加2点):

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

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

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)

然而,在我们开始的地方结束是一种很好的做法。为什么 ?因为如果循环结束它开始的另一个单元格,我们不能假设单元格指针将在哪里。老实说,这种做法让brainfuck 少了brainfuck。

于 2013-11-08T22:27:38.143 回答
55

维基百科有代码的注释版本。

+++++ +++++             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 递增 10 次来初始化。从第 2 行开始的循环有效地设置了数组的初始值:(a[1] = 70接近 72,字符“H”的 ASCII 码),a[2] = 100(接近 101 或“e” ),a[3] = 30(接近 32,空格的代码)和a[4] = 10(换行符)。a[1]该循环通过将 7、10、3 和 1 添加到单元格,和 1 来工作a[2]a[3]并且a[4]每次通过循环 - 每个单元格总共添加 10 次(给予 a[1]=70等)。循环结束后,a[0]为零。>++.然后将指针移动到a[1],它包含 70,向它添加 2(产生 72,它是大写 H 的 ASCII 字符代码),并输出它。

下一行将数组指针移到a[2]并添加一个,产生 101,一个小写的“e”,然后输出。

因为 'l' 恰好是 'e' 之后的第七个字母,所以要输出 'll' 会添加另外七个字母(+++++++),a[2]结果输出两次。

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

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

于 2013-05-30T13:06:37.717 回答
11

Brainfuck 和它的名字一样。它仅使用 8 个字符> [ . ] , - +,这使其成为学习最快最难实现和理解的编程语言。 ……让你最终以他妈的你的大脑告终。

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

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

  1. >指针向右移动 1

  2. <将指针向左移动 1

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

  4. -将元素的值增加 1

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

  6. ,将输入输入到当前单元格。

  7. [ ]循环,+++[ -] 3 个计数的计数器,因为它前面有 3 个“+”,并且 - 将计数变量减 1 个值。

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

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

恭喜!你已经学会了BF的语法

————更多——</p>

让我们制作我们的第一个程序,即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) # prints 'H'

>+.

移动到第二个单元格增量 1 到它的值 100+1 并打印('.')它的值,即 chr(101) chr(101) #prints 'e' 现在下一块没有 > 或 < 所以它取现值最新元素并仅增加它

+++++ ++..

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

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

———<strong>用在什么地方?———-

它只是一种用来挑战程序员的笑话语言,实际上并没有在任何地方使用。

于 2018-01-11T09:16:16.153 回答
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)
于 2017-03-20T06:34:39.167 回答
4

所有的答案都很详尽,但缺少一个小细节:印刷。在构建您的brainfuck 翻译器时,您还考虑了字符.,这实际上是brainfuck 中打印语句的样子。所以你的脑残翻译应该做的是,每当它遇到一个.字符时,它就会打印当前指向的字节。

例子:

假设你有 --> char *ptr = [0] [0] [0] [97] [0]... 如果这是一个愚蠢的声明: >>>.你的指针应该移动 3 个空格到右着陆:[97],所以现在*ptr = 97,在你的翻译遇到 a.之后,它应该调用

write(1, ptr, 1)

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

于 2016-07-18T10:00:21.503 回答
2

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

因此解析器将逐行读取您的代码,并说好的有一个 > 符号所以我必须提前内存位置,代码很简单,如果(该内存位置中的内容)==>,memlocation =+ memlocation 这是用更高级别的语言编写,类似地如果(内存位置的内容)==“。”,然后打印(内存位置的内容)。

希望这可以清除它。tc

于 2018-07-29T10:31:54.243 回答