在讀Learn you a haskell for great good時(附帶一提,這本書的插圖好可愛,看的時候會開心^^),和某位苦主(看這裡)一樣有個疑問:為什麼foldr
可以處理無限長的串列但是foldl
卻不行?
看了好幾次foldl
和foldr
的定義,都還沒參透的,資質駑鈍的我,只好求助於Google大神。
在讀Learn you a haskell for great good時(附帶一提,這本書的插圖好可愛,看的時候會開心^^),和某位苦主(看這裡)一樣有個疑問:為什麼foldr
可以處理無限長的串列但是foldl
卻不行?
看了好幾次foldl
和foldr
的定義,都還沒參透的,資質駑鈍的我,只好求助於Google大神。
打從知道可以寫程式和電腦說話之後,只要有空閒,我就會去學習程式語言。我的朋友很少,想說如果學程式語言的話,至少可以有人(?)陪我聊天...
語言是兩個個體之間溝通的工具。無論是人與人之間,或是人與機器之間,若要互相了解,語言是不可或缺的。為了可以看得懂來自外國的訊息,我們會學習英文、日文,甚至是西班牙文、法文等。(附帶一提,我對阿拉伯文有興趣)為了要讓電腦了解我們需要的幫助,我們使用程式語言來描述我們的願望。無論是哪一種情況,語言都是我們讓對方明瞭自己的意志、願望、甚至是感情的工具。
一個語言可以反映出其使用者的文化,讓我們更加了解以這些語言為母語的人們。例如說,學習過日文的人都知道,日文對於兩性之間,以及階級之間的分別相當明顯。以第一人稱詞(我)來說,有男女通用的「わたし(私)」,女性限定的「あたし」,男生較常使用的「ぼく(僕)」,以及男性限定的「おれ(俺)」。程式語言也一樣。Perl,如大家所知道的一樣,是一個具有強大文字處理能力的程式語言。由shell script演化過來的Perl是我所知道唯一一個把正規表達式包含在語言中的程式語言。
為了解土生土長的台灣,以及源遠流長的中華文化,我學習中文;為了解美國的自由、民主、科學與技術,我學習英文;為了解許多稀奇古怪點子的來源,我學習日文。如果還有時間的話,我還希望可以學習阿拉伯文。我想在學阿拉伯文的過程,我可以稍微看見以前那個幅員廣大的阿拉伯帝國以及鄂圖曼帝國,也可以了解以阿拉伯文撰寫的可蘭經真正的內含吧?
程式語言在一開始是設計給電腦看的,就本質上來說應該缺乏感情,或者說,不太容易看到蘊含豐富感情的程式碼。但如果作者傾注他對這份程式的愛,那麼讀這份程式碼的人也必然可以感受的到這股溫暖而強烈的情感。而作者在寫作程式時所遇到的挫折,也會以各種不同的形式讓讀者們看見。(有看過把怨念直接寫在註解裡的人)
所以呢,學習程式語言對我來說不是駕馭電腦的手段,而是一個讓我更能夠了解電腦的一個管道。交朋友的時候,往往會在了解對方更多之後感情更好;對機器也是一樣,在了解機器之後,你就可以理解他的脾氣,也就可以知道機器不合作的理由,甚至是增進你和機器的合作關係。
有人說,學習不同的自然語言(自然演化出的語言)除了上面所說的好處之外,也有助於大腦的健康。所以我希望可以找時間學習阿拉伯文。我很想了解在這個龍飛鳳舞的文字裡面可以讓我窺見的事情。在程式語言方面,對於haskell的許多特點,我很好奇。所以就決定來學haskell啦!
謹向給予我希望的那位可愛的女神祈求,使我可以逐步的完成我的願望。
首先祝各位親愛的讀者聖誕節快樂喔!這次的主題是組合語言中小數的操作。感覺起來很無聊?沒錯啦,這主題本身連我聽起來連我都覺得有些無聊。但我還是想要把它貼出來,因為呢,藏在主題下面的東西其實有些小故事可以說。也就是,本文的程式碼是講無趣小故事的藉口。
親愛的讀者,如果之前學習過,或者是欣賞過32位元x86組合語言程式的話(附帶一提,那裡的呼叫協定真的是一團糟),可能就知道在該平台上計算小數的主流是x87 fpu指令。x87 fpu指令可以做純量的數學計算,並且配有80-bit浮點數的計算能力。然而,因為x87暫存器設計的關係,個人覺得該指令使用的難度比較高,用起來比較有被綁手的感覺。
而現在,具有x86-64處理器的cpu都具有SSE指令。這套指令裡面就配有小數純量處理的指令,暫存器的使用卻更加的有彈性,用起來也比較開心。於是,在大部份的狀況下,小數的計算利用SSE指令完成,而參數的傳遞,和x64 ABI所說的一樣,也是利用SSE的暫存器(xmm暫存器)來傳遞。
還先不用替x87 fpu掉眼淚。x87指令相對於SSE還是有不可取代的優勢。有些數學函數,像是sin, cos等,如果想要靠硬體來完成,那只能求x87 fpu了。讓x87得以保留飯碗的另一個因素是80-bit浮點數計算能力。總之x87在這幾年應該不會失業,請各位放心 ^_^
剩下的呢,請各位親愛的讀者欣賞一下簡單的程式小品囉。
在寫程式的時候,我們不斷的重複著修改、測試、修改、測試...的流程。顯而易見的,我們要作不少重複的測試。在理想的情況下,當然是希望親愛的電腦可以替我們代勞:只要我說「請跑測試吧」,電腦就會進行測試,並且告訴我們哪個測試沒有通過。其實,這樣的好事不是天方夜譚,而是一個應該要應用到每個你心愛的程式上面的作業流程!
其中一種測試的方法,把整個程式切割成許多可以測試的小單位,然後依據這些小單位的功能進行一連串的測試。這樣的方式被稱作unit test。
之前的兩篇(這個和這個)組語文裡面是直接使用system call來完成我們的願望。當然,就發現新大陸(?)的角度來看,這是相當有趣的。但如果想要快一點用組合語言來放煙火,那麼直接使用現有的程式庫會比較快。
在組合語言裡面,要呼叫程式庫的功能(用稍微專業點的詞彙來表達的話,是指函數(function)),需要遵守一些既定的規則。就像是親愛的讀者,如果想要搭捷運,就必須在出捷運站以及進捷運站的時候刷卡,不然就算是犯規了。
這套規則主要在描述一個函數被呼叫的時候,必須要怎麼樣安排參數,以及哪些暫存器是可能被更改的,哪些絕對會被保留下來等等的規定。詳細的內容請參考這份文件。
接著來看程式碼吧!想編譯以下的程式碼的話,使用:
gcc put.s
.section .text /* 這次我們用像C語言一樣的作法來寫 這次的程式:使用main當作程式的起 點 */ .global main .type main, @function main: /* x86-64 ABI (user mode) 參數依照下面的順序傳遞: rdi rsi rdx rcx r8 r9 puts有一個參數:要印出資料的指標 必須以NULL結尾。 */ mov $hello_str, %rdi call puts /* rax是函數的return value 我們希望main傳回0,所以設定 rax = 0 xor %rax, %rax 效果等同於 mov $0, %rax */ xor %rax, %rax ret .section .rodata hello_str: /* .string 後面所接的字串,會由 組譯器補上'\0' (NULL) 所以,下面的字串會變成 "Hello!\0" 總之,由.string所形成的字串常數 會變成可以在C語言的世界中方便使用 的字串喔! */ .string "Hello!"
其實如果很熟x86-64 ABI的人就會發現這個程式碼應該有可以補強的地方。親愛的讀者,看得出來嗎?
上一次的程式裡面使用了簡單的系統呼叫,不過很明顯的,什麼事情都不會做的程式應該沒有任何的魅力吧?那麼,我們何不讓這個程式和我們說幾句話?
就system call的角度來看,要讓程式和外界有所溝通,自然會想到的是read, write等system call。(可以去翻翻asm/unistd_64.h喔~)
.section .text .global _start .type _start, @function _start: /* syscall : write 依據asm/unistd_64.h,它的system call number為1 依據man page (在terminal中輸入man 2 write ), 它有三個參數: 參數一(rdi):file descriptor. 1: stdout 參數二(rsi):裝有想要輸出資料的一塊記憶體空間 的指標 參數三(rdx):上面所說的,那個記憶體空間中的資 料大小 */ mov $1, %rax mov $1, %rdi mov $_start_hello_str, %rsi mov _start_hello_str_size, %rdx syscall /* syscall : exit syscall number為60 */ mov $60, %rax mov $0, %rdi syscall .section .rodata /*read-only data*/ _start_hello_str: .string "Hello!\n" _start_hello_str_size: /* _start_hello_str_size這個標籤所在的位置減去 _start_hello_str,恰好就是Hello字串的長度。 */ .quad _start_hello_str_size - _start_hello_str
想要編譯它的話,請使用下面的指令:
gcc -nostartfiles hello.s我想該寫的都寫在註解上了吧!那麼,請慢用囉~
不想看作文的看官,請點這裡吧!
今天一回家,打開電腦,登入,準備看新聞娛樂更新等等的時候...
「奇怪,按下Alt+F2怎麼沒反應?」(連按)「難不成...」
我試著啟動dolphin,KDE4的預設檔案管理程式。果然...
「沒有啟動...所以應該就是ktimezone了吧?再確定一下好了...」
我點了下左下方的開始功能表,看到它瞬間彈起來的我鬆了口氣,並馬上開始呼叫親愛的konsole同學。對我來說,只要有他,控制整個系統基本上沒問題。
「konsole同學!」
『來了~』
令人安心的黑色視窗呢!
「執行rm ~/.kde/share/config/ktimezonedrc
,把ktimezonedrc砍掉吧!」我下了這樣的命令。
『OKOK!』
才說完,那個檔案就從硬碟中被抹除了,「接下來只要登出就好了吧」我這樣想著
回到開始功能表,我打算正常的登出,但很可惜的,登出似乎也受到這個錯誤的影響而無法被啟動...
此時我想到了那個大絕...不到危及時刻絕對不能用的大絕招:Ctrl-Alt-Backspace。
「怎饃辦...算了,賭不會出事吧!」
賭徒魂覺醒的我,便按下了這組致命的快捷鍵。畫面瞬間變得黑暗,並且回到了登入畫面。輸入密碼後,帶著忐忑不安的心情,按下了Alt+F2...
KRunner馬上出現,並且呈現待命狀態。
「終於...」
來描述一下症狀吧!不知道症狀的話就沒辦法迅速的解決問題了對吧?
簡單的說,這樣的問題有下面的解決方案:
很多人都有在blog上面分享程式碼的經驗吧。有些人分享的程式碼沒有經過排版,很難看得懂並利用;有些人的程式碼有排好版,很美觀,也易於再利用(利用<pre>標籤)。但這時候,腦中會浮現一個「想要讓程式碼更漂亮」的念頭:希望程式馬可以有美美的色彩,就像自己的編輯器一樣。我想讀者們也一樣,喜歡看有美美程式碼的部落格吧?
如果你很幸運的,使用emacs當作你的日常編輯程式的話,有個現成的解決方案,叫做htmlize(請Google它,或點這裡)。它可以把你的程式碼,依照emacs的套色方式產生與其對應的html碼。你可以打開所產生的html碼,把相關的部份貼到你的部落格上,或者是像某神人一樣,寫出自訂的emacs lisp來直接輸出適合貼到部落格上的html碼,然後直接貼上。
htmlize有一個好處是,它產生的html碼可以搭配自訂的CSS。藉著使用自訂的CSS,你可以讓貼上的程式碼以你想要的色彩、背景顏色來呈現。
要使用htmlize, 在emacs環境下:
M-x htmlize-buffer
完成後,emacs會把所產生的html碼放到新的buffer中。此時,你可以把程式碼存起來,或者是剪貼相關的部份並且貼到部落格中。所謂相關的部份,是指<script type="text/css">
中的所有內容,以及<pre>
中的所有內容。
不知道美化的效果如何?各位讀者們可以參考:
讓程式碼變得美美的,心情也會變好呢!有天,突然臨時起意想要學習組合語言,所以呢首先來熟悉一下一些基本的東西。本程式唯一的功能,是利用sys_exit,一個system call,來結束程式,並把exit code設定為0。(OS:好心酸)。程式碼如下:
.text .global _start .type _start, @function /* _start是真正的程式進入點 在C語言中,編譯器會幫你產生一個可以導向到main的_start函數 */ _start: /* 注意: 要把常數輸送到暫存器,使用 mov $60, %rax 使用下面的指令會把記憶體位置 60 (0x3C)的資料傳送到rax中。 mov 60, %rax sys_exit, syscall number為60 (依據asm/unistd_64.h) 第一個參數為exit code */ mov $60, %rax /*syscall number傳送到 rax*/ mov $0, %rdi /*第一號參數傳送到 rdi*/ syscall /*調用sys_exit。程式在此領便當。*/ /* 不能用ret來結束。 _start是「創世函數」,而ret是用來把控制權轉交給呼叫此函數的函數 所以ret會導致不可預期的結果發生 C語言的main可以用return(被翻譯成ret),是因為main可以回到_start */
想要編譯它的話請使用下面的指令:
gcc -nostartfiles exit.s
這份程式碼使用的x86-64語法是AT&T的語法(AT&T syntax)和Intel官方的語法有很大的不同。其中最重要也是最主要的差異是在運算為的排放。舉例來說,Intel syntax下,把60放到rax暫存器為:
mov rax, 60
但在AT&T syntax下則是:
mov $60, %rax
大部分的x86-64程式設計人員使用的是Intel syntax。但私心覺得AT&T syntax看起來很順眼,而且GNU assembler使用這樣的語法,所以就決定學這一套了。
回到本程式的另一個重點,system call的使用。System call是直接和Linux kernel溝通的方法,可以調用kernel所提供的一些基本功能。依據x86-64 ABI(詳細見這裡),要進行system call,首先要在rax暫存器上面填入system call number,接著,在依照rdi, rsi, rdx, r10, r8, r9的順序把參數填入。在這個程式裡面,因為sys_exit的參數只有一個,所以把它填入rdi中。完成上面的佈置後,利用syscall指令來進行system call。