CTF_Brain Fuck語(yǔ)言

Brainfuck是一門(mén)晦澀難以捉摸的語(yǔ)言巨星,這個(gè)語(yǔ)言是出了名的難以編寫(xiě),只有 8 個(gè)簡(jiǎn)單命令與 1 個(gè)指令指標(biāo)。Brainfuck 大概只是為了挑戰(zhàn)程式設(shè)計(jì)師,或純粹娛樂(lè)用,沒(méi)有什麼實(shí)際作用。Urban Müller 於 1993 年創(chuàng)造了 Brainfuck,以下是用這門(mén)語(yǔ)言編寫(xiě)「Hello World!」的樣子:

首先談到這個(gè)語(yǔ)言的定義和運(yùn)行原理該語(yǔ)言定義在這樣一個(gè)環(huán)境之上:你有一列無(wú)限長(zhǎng)的小火車(chē),每個(gè)車(chē)廂里裝了一個(gè)數(shù)字,初始為0。還有一個(gè)列車(chē)員,初始在最頭上那節(jié)車(chē)廂上。好了,你把你寫(xiě)的BrainFK程序交給列車(chē)員,列車(chē)員會(huì)做如下的事情:從左向右、由上自下一個(gè)字符一個(gè)字符地讀取你的程序

當(dāng)讀到+的時(shí)候,將所在車(chē)廂里的數(shù)字加一
當(dāng)讀到-的時(shí)候,將所在的車(chē)廂里的數(shù)字減一
當(dāng)讀到>的時(shí)候,跑到后一個(gè)車(chē)廂去
當(dāng)讀到<的時(shí)候,跑到前一個(gè)車(chē)廂去
當(dāng)讀到[的時(shí)候,如果該車(chē)廂里面的數(shù)字為0,則跳去執(zhí)行下一個(gè)]之后的程序內(nèi)容
當(dāng)讀到]的時(shí)候,如果該車(chē)想里面的數(shù)字不為0,則跳去執(zhí)行上一個(gè)[之后的程序內(nèi)容
當(dāng)讀到.的時(shí)候,將所在車(chē)廂里面的數(shù)字翻譯成ASCII字符,顯示在你的屏幕上
當(dāng)讀到,的時(shí)候,從等待使用者輸入一個(gè)ASCII字符,轉(zhuǎn)碼成數(shù)字寫(xiě)進(jìn)所在車(chē)廂里

接下來(lái)讓我們仔細(xì)看這段程序:++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.

加上換行整理一下,將其變?yōu)椋?/p>

++++++++++
[

+++++++
++++++++++
+++

<<<<-
]

++.
+.
+++++++..
+++.
++.
<<+++++++++++++++.
.
+++.
------.
--------.
+.
.
'''
'

為了提高可讀性,我在此(狂妄地)引入一個(gè)宏:讓我們用 oX 來(lái)代替連續(xù)的X個(gè)o號(hào)吧!(比如 +5 <=> +++++
為了進(jìn)一步提高可讀性,我在此(更加狂妄地)引入一個(gè)宏:讓我們用 // 在后方形成注釋吧!

于是上面的代碼變成

+10 //火車(chē)頭車(chē)廂置為10
[

+7 //下一個(gè)車(chē)廂值加 7
+10 //下一個(gè)車(chē)廂值加 10
+3 //下一個(gè)車(chē)廂值加 3
+1 //下一個(gè)車(chē)廂值加 1
<4 - //前移四個(gè)車(chē)廂,并將其值減 1
] //此時(shí)列車(chē)員在第0車(chē)廂,如果該車(chē)廂內(nèi)數(shù)字不為0,則跳轉(zhuǎn)到前面的[之后
//稍有常識(shí)的人都能看出,只要我們的程序執(zhí)行到這里之后
//前五個(gè)車(chē)廂的最終值就分別為 0, 70, 100, 30, 10
//此時(shí)列車(chē)員在第 0 車(chē)廂

+2 . //向后一車(chē)廂并+2然后輸出到屏幕,此時(shí)列車(chē)員在第1車(chē)廂,ASCII(72)='H'

+1 . //向后一車(chē)廂并+1然后輸出到屏幕,此時(shí)列車(chē)員在第2車(chē)廂,ASCII(101)='e'
+7 .. //列車(chē)員不動(dòng),將所在(第2)車(chē)廂內(nèi)容繼續(xù)加7之后輸出兩次,ASCII(108)='l'
+3 . //列車(chē)員不動(dòng),將所在(第2)車(chē)廂內(nèi)容繼續(xù)加3之后輸出,ASCII(111)='o'

+2 . //向后一車(chē)廂并+2然后輸出,此時(shí)列車(chē)員在第3車(chē)廂,ASCII(32)='<空格>'

<2 +15 . //向前兩車(chē)廂并+15然后輸出,此時(shí)列車(chē)員在第1車(chē)廂,ASCII(87)='W'

. //向后一車(chē)廂并直接輸出,此時(shí)列車(chē)員在第2車(chē)廂,該車(chē)廂保留原值111,ASCII(111)='o'
+3 . //不動(dòng),+3而后輸出,ASCII(114)='r'
-6 . //ASCII(108)='l'
-8 . //ASCII(100)='e'

+1 . //再向后一車(chē)廂,此時(shí)列車(chē)員在第3車(chē)廂,+1,ASCII(33)='!'

. //再向后一車(chē)廂,此時(shí)列車(chē)員在第4車(chē)廂,直接輸出,ASCII(10)='<換行符>'

所以事實(shí)上只用一個(gè)車(chē)廂就能寫(xiě)就這個(gè)helloword,但是那樣寫(xiě)程序就太長(zhǎng)太沒(méi)有可讀性了。所以我們需要在前面用一個(gè)循環(huán),來(lái)初始化70,100,30,10四個(gè)車(chē)廂(并且預(yù)先用第0個(gè)車(chē)廂來(lái)標(biāo)記循環(huán)次數(shù)),這四個(gè)車(chē)廂分別提供了對(duì)大寫(xiě)字母、小寫(xiě)字母、特殊文字符號(hào)、特殊控制符號(hào)的方便訪問(wèn)。說(shuō)起來(lái),這個(gè)語(yǔ)言簡(jiǎn)單優(yōu)雅易學(xué),一旦熟悉之后,如果能借助上面定義的數(shù)字宏,寫(xiě)起來(lái)方便快捷讀起來(lái)行云流水,簡(jiǎn)直就是數(shù)組處理界的Regex(知乎為什么不能給字加刪除線(xiàn)。。)用這個(gè)語(yǔ)言可以有很多趣題,比如說(shuō)我以前曾經(jīng)想過(guò)一個(gè)問(wèn)題:能不能實(shí)現(xiàn)一個(gè)動(dòng)態(tài)指針,即列車(chē)員讀取某車(chē)廂的數(shù)值,然后向后走該數(shù)值所表述數(shù)量的車(chē)廂,以操作目標(biāo)車(chē)廂的數(shù)字。
這個(gè)問(wèn)題后來(lái)是這樣解決的(摘自我之前的某個(gè)博客),下面這段文字里,[X]代表第X節(jié)車(chē)廂,指針即是列車(chē)員:
經(jīng)過(guò)這樣的討論,之前的“是否可以實(shí)現(xiàn)指針的動(dòng)態(tài)定位”問(wèn)題變成了下面的問(wèn)題:是否可以找到一種方法,讓指針查找的時(shí)候,每一次移動(dòng)都能夠找回原來(lái)的位置。在這里解釋一下,比如[4]用來(lái)存儲(chǔ)另一個(gè)地址,或者說(shuō)[4]中存儲(chǔ)著一個(gè)自定義的指針,如果*[4] = 8,那么當(dāng)程序執(zhí)行到[4]的時(shí)候應(yīng)該將主指針定位到[12] // (12 = 8+4)

我們用BF語(yǔ)言試寫(xiě)一下:我們先初始化主指針到[4]:>>>>接下來(lái)我們將指針自增 [4] = 8 次,問(wèn)題就來(lái)了如果我們只要自增八次,我們只要寫(xiě)下>>>>>>>>即可但是我們?cè)诰帉?xiě)程序的時(shí)候可能并不知道[4]的值,或者說(shuō),[4]里面是動(dòng)態(tài)的最早想到的一個(gè)辦法是指針每自增一次,[4] - 1,但是事實(shí)上 [4] - 1 這個(gè)語(yǔ)句在BF語(yǔ)言中就是一個(gè) '-' ,但是主指針必須指向[4]。如果我們寫(xiě) [><-] 那么到最后指針還是停留在[4]上如果我們把過(guò)程拆開(kāi),就是[4]值先自減,指針自增,然后自減,值自減,然后自增兩次,再自減兩次,值自減,自增三次,自減三次,值自減,以此類(lèi)推,直到[4] = 0,然后指針自增8次
如果看到這你還沒(méi)笑,那你看了下面的代碼,一定就笑了:

  • [

<-

<<-

<<<-

<<<<-

<<<<<-

<<<<<<-

<<<<<<<-
] >>>>>>>>
真是笑死了,這和>>>>>>>>沒(méi)有任何區(qū)別,[]循環(huán)形同虛設(shè),還浪費(fèi)資源,到頭來(lái)編寫(xiě)者還是要在寫(xiě)程序時(shí)得知*[4]
上面的代碼唯一值得贊揚(yáng)的就是畫(huà)面美感
當(dāng)然如果你寫(xiě)成
-[><->><<->>><<<->>>><<<<->>>>><<<<<->>>>>><<<<<<->>>>>>><<<<<<<-] >>>>>>>>
那就連畫(huà)面美感也沒(méi)了

那么到底有沒(méi)有一種辦法可以在指針自增的同時(shí),讓其還可以每一次自增之后再找回原來(lái)的[4]呢。我腦袋比較笨,在討論的當(dāng)天并沒(méi)有想出一個(gè)可行的算法來(lái)。想了很多很多辦法,包括用五個(gè)連續(xù)的地址存儲(chǔ)一個(gè)變量,[n]存儲(chǔ)值,[n+1],[n-1],[n+2][n-2]分別存儲(chǔ)變量的左右信息等等,但最后都被證明不可行。然而當(dāng)天清晨,我鬼使神差地夢(mèng)到了自己用冒泡排序算法解題。醒來(lái)一拍大腿,哎呀我C,成了。

我所想到的算法類(lèi)似冒泡排序中冒泡的過(guò)程,如下:
指針每自增一次,[n]與[n-1]的值便交換,然后*[n]自減,重復(fù)這一過(guò)程直到 [n] = 0
[n]與[n-1]的值交換,可以有兩種方法實(shí)現(xiàn),一種是對(duì)
[n] 和 *[n-1] 進(jìn)行異或,還有一種就是利用第三方[m]
鄙人不才,尚未找到在BF中實(shí)現(xiàn)抑或的辦法,所以決定使用后者。

那么初步的算法如下:
規(guī)定當(dāng)[n]表示一個(gè)地址時(shí),[n-1]必須為零,作為臨時(shí)區(qū)域,比如上面的例子,就必須*[4] = 8, *[3] = 0,該指針變量由[3]和[4]構(gòu)成,長(zhǎng)度為2,頭地址為[3]。
1 指針指向[n+2],將當(dāng)前指針位記為[n]
2 [n-2] = [n]; [n] = [n-1]; [n-1] = [0]
3 [n]--
4 如果[n+1] 非零,跳轉(zhuǎn)到1
5 指針自減2

這個(gè)算法用BF語(yǔ)言書(shū)寫(xiě)如下:
'''
[

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

-<
]<<
'''
當(dāng)然,如果你嫌行數(shù)太多看著難受,也可以寫(xiě)成這樣
[>>[<<+>>-]<[>+<-]>-<]<<

接下來(lái)把后面的所有內(nèi)容向前移動(dòng)兩格,填充那個(gè)多出來(lái)的部分就行了

當(dāng)然,這段文字提出的方法是有缺陷的:當(dāng)列車(chē)員被正確定位之后,定位列車(chē)員所使用的那個(gè)車(chē)廂就被置0而無(wú)法再次使用(變量被破壞)了。
這個(gè)缺陷也是有解決方案,可以自己試試看哈哈哈哈

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容