2012年4月5日 星期四

喜歡上一個漫畫家「冬目景」

喜歡上一個漫畫家「冬目景」。

最早是在,可能有十年了吧,在表弟家看到的一本漫畫叫作《黑鐵》。一個人被改造成機器人的劍客故事,聽起來既白癡又低能,不知道是搞笑漫畫還是熱血打鬥,但是實際上卻是一點點抑鬱的,很觸動人心深處的故事,跟劍客不劍客沒什麼關係。就像很多科幻小說,除去科幻的部份,卻還是很精彩,我想,好的故事,跟背景設定無關,而是故事本身的重點是在人身上,而黑鐵就是這樣的故事,重點在人身上。後來在清大念研究所,宿舍的交誼廳也放著一套黑鐵,但是並沒有全部,這次我很仔細的全部看了,更喜歡這個漫畫了,我發現他不只是故事好,畫面也總是帶一點點悲戚的美,歹勢阿,我文筆很爛,想不到什麼好詞描述,不過就是這樣。

過了幾年的現在,又想到這部漫畫,想起來,想去把結局也看完,上往搜索了一下,才發現這個漫畫連載中斷,所以沒有結局了。也知道了作者叫作「冬目景」,ㄟ,我發現我看過她的《白老鼠遊戲》但是卻沒什麼特別的印象。發現冬目景的作品很多都中斷連載,不知道原因,只覺得很可惜。看了一些作品的介紹,看到《昨日之歌》好像會是我喜歡的類型的故事,就去找來看了,結果……真的很喜歡,只是看的過程常常會不小心的掉下眼淚,冬目景的畫真的很漂亮 ^^ 應該說畫面看起來常常髒髒的,筆觸也斷斷續續的,但是看起來就是很漂亮,筆觸成熟,看起來很隨意,不會講,懶的寫了 @_@ 反正很喜歡,害我很想寫部落格,不過我寫不出什麼毛,所以還是停吧...

2011年11月4日 星期五

Vim 自動提示編譯錯

Vim error highlight

2013/08/12 更新[[[ 自從 YouCompleteMe 這個 plugin 出來之後,一堆 plugin 都變成垃圾了,包含這篇文章的所有內容,也變成垃圾了,請直接參照 https://github.com/Valloric/YouCompleteMe 然後不用再往下讀了。 ]]]

一直想要一種功能,就是 vim 可以自動顯示錯誤的程式碼,像是 visual studio 或是 eclipse 那樣。最近摸索了一下終於讓我實現了這個功能 :P

事先準備

  • AsyncCommand plugin

    可以在背景執行動作,不會妨礙現在進行的編輯作業。

  • errormarker plugin

    可以把 quick fix list 裡面的錯誤,轉而使用 syntex highlight 直接在編輯視窗內標示出來,看一下官方網站的 screensho:

    http://mh21.piware.de/vim-error-markers-doxygen.png

進行修改

理論上這兩個東西都有安裝的話,好像就成功了,不但可以在背景編譯,還可以在編輯畫面表示錯誤。當然,實際上這兩個都安裝了以後,還是沒辦法直接達成目的的,不然我也不用寫這篇文了 XD

經過 trace 兩者的 script 以後,發現原因在於 errormarker 是透過 hook QuickFixCmdPost event 來呼叫 SetErrorMarkers() 函數來把 quick fix list 裡面的資料轉標到編輯畫面。當平常使用 :make 的時候,QuickFixCmdPost 會被觸發,但是當透過 AsyncCommand 進行 make 的時候,似乎不會觸發 QuickFixCmdPost 事件,於是 SetErrorMarkers() 也不會被呼叫,然後一切都完啦。所以我們的目標就是讓 AsyncCommand 「正確」的呼叫 SetErrorMarker() 就好。

先講一下 AsyncCommand 的機制,AsyncMake 其實是一個 wrapper,包住了 asynccommand#run(make, asynchandler#qf) 這個動作,後面那個 asynchandler#qf 是等到 make 結束以後,用來處理結果的 handler,而這個 handler 會在 asynccommand#done() 的時候被呼叫,所以我們只要在 asynccommand#done() 裡面呼叫 SetErrorMarkers() 就可以了。

asynccommand#done().vim/bundle/asynccommand/autoload/asynccommand.vim 這個檔案裡面,找到他,

 1   function! asynccommand#done(temp_file_name)
 2     " Called on completion of the task
 3     let r = s:receivers[a:temp_file_name] "handlers are registered in s:receivers
 4     if type(r.dict) == type({})
 5       call call(r.func, [a:temp_file_name], r.dict)
 6     else
 7       call call(r.func, [a:temp_file_name])
 8     endif
 9     unlet s:receivers[a:temp_file_name]
10     delete a:temp_file_name
16   endfunction

在函數結束之前加上呼叫 SetErrorMarkers() 的程式碼,變成這樣:

 1   function! asynccommand#done(temp_file_name)
 2     " Called on completion of the task
 3     let r = s:receivers[a:temp_file_name] "handlers are registered in s:receivers
 4     if type(r.dict) == type({})
 5       call call(r.func, [a:temp_file_name], r.dict)
 6     else
 7       call call(r.func, [a:temp_file_name])
 8     endif
 9     unlet s:receivers[a:temp_file_name]
10     delete a:temp_file_name
11
12     if exists("*ExposeSetErrorMarkers") " Check and
13       call ExposeSetErrorMarkers()      " Call ExposeSetErrorMarkers()
14     endif                               " here
15     
16   endfunction

注意到我們呼叫的不是 SetErrorMarkers() 而是 ExposeSetErrorMarkers(),這是因為 SetErrorMarkers() 是一個 script local 函數,只能在 script 內部被呼叫,所以我在 errormarker.vim 裡面加了一個函數來曝光 SetErrorMarkers()

1   function! ExposeSetErrorMarkers()
2       call s:SetErrorMarkers()
3   endfunction

這樣以後只要輸入 AsyncMake 就可以在背景編譯,編譯完成以後自動 highlight 錯誤了。為了方便使用,我還加上了幾個 autocommand:

1   au BufWritePost *.h   AsyncMake
2   au BufWritePost *.c   AsyncMake
3   au BufWritePost *.cc  AsyncMake
4   au BufWritePost *.cpp AsyncMake

這樣編輯 c/c++ 的時候每次存檔都會自動編譯然後 highlight 錯誤。

其實我知道我的改法很醜啦,但是他 work XD 如果有人知道比較優雅的改法請跟我分享阿~

小記:我發現我 ubuntu 安裝 vim 的話,是沒辦法作這件事的,執行 script 的時候會發生錯誤,但是安裝 vim-gtk 的話就可以了,不知道是因為設定的時候哪個選項沒開,但是總之如果有人遇到一樣的問題的話請試試看安裝 vim-gtk。

2011年9月26日 星期一

眷村有個每天騎腳踏車巡邏的老人今天睡覺沒醒


這個老人每天都打著赤膊,騎著快解體的淑女車,大街小巷的到處跑,也沒有要去哪裡,反正就是一直到處騎,但是範圍非常的大,航跨十公里的範圍內都有機會看到他打著赤膊慢慢的騎著腳踏車,雖然沒有人從頭到尾跟著他走過一趟,但是我相信他每天的路線應該都是一樣的。

騎了幾十年了,大家都認識他了,他以前是一邊騎,一手抓著米酒頭在灌,臉紅紅的,身體也紅紅的。十幾年前開始,酒沒了,取而代之的是菸,一邊慢慢的騎車一邊抽菸。手上沒菸的時候如果遇見認識的人,就會先擋兩根來(也不會還 XD)。像我小舅舅每天晚上固定的時間就會在外面抽菸,也固定給經過的他兩根菸。

厲害的是他向來都只穿一條褲子,從來不穿上衣。除了有一年冬天超冷,某天他穿了一件短袖上衣出來,大家沒認出是他 XD


外婆說今天上午那人一樣到處騎腳踏車,遇見認識的人也一樣擋兩根菸,一樣都是活跳跳的,中午那人吃飽了以後去睡午覺,下午快五點的時候,眷村裡面其他的老人去他家要去找他吃晚餐的時候,發現他還在睡。

其實永和這個大陳義胞五和新村已經很老了,眷村裡面,老一輩的,小時候跟著兩蔣來台灣的,現在的年紀最起碼都六七十起跳,都是隨時就會回家的狀態。眷村裡面,巷弄間,總是家家戶戶的人都坐在門口的藤椅上聊天,誰家發生什麼事,誰家女兒嫁人了又離婚了,誰家曾孫出生了,大家都知道。這些年來,每兩三年總有一個兩個人就不見了,就不會再出現了。

所以當鄰居發現他還在睡的時候,也沒很驚訝。因為這也沒什麼,就是回家時候到了。通知了還在陽明醫學院唸書的女兒,救護車來就把人帶走了。

外婆:「早上騎車的時候還活跳跳的,怎麼中午睡覺就不醒了 -_-」
小舅:「本來每天都被要兩支菸還蠻賭爛的,久了也有感情了....」

至少是個走的安詳。

2011年9月24日 星期六

我外婆,在美國

聽外婆講了幾個當初她跟外公到美國去生活發生的笨事,覺得很有趣,記錄下來。

當初外婆跟外公兩個人,年輕的時候,就這樣跑去美國了,英文也不會講,只會很簡單的 yes 跟 no 之類的。但是還是就這樣跑去美國了,準備開始過他們的新生活。

到美國的時候,天氣跟台灣不一樣,空氣很乾,外婆走在路上,覺得臉很不舒服,因為臉都乾掉了,而且也很冷,不知道怎麼辦之際,看到旁邊的百貨公司,竟然有很多沒人要的透明塑膠袋,看起來品質很好,很厚,恨透明,而且還很乾淨,外婆異想天開,想說既然很乾淨、又沒有人要,外婆就把塑膠袋撿來,然後往自己的頭上一套,既可以擋風,又可以保住溼氣,還可以看到外面,所以可以正常走路沒問題。「我真聰明,呵呵」外婆說。塑膠袋套好以後,外婆把原來連帽大衣的帽子也套上,蓋在塑膠袋外面,便更覺溫暖。那知道,一個不小心吸氣的時候,塑膠袋竟然就捂住口鼻了,一下子不能呼吸,外婆急了,就大口吸氣,但是塑膠袋就包的更緊,然後因為急了,也忘記要先把帽子掀開,結果塑膠袋要往上拉開就一直被帽子擋住,塑膠袋一直拉不開阿!一口氣上不來就快要死啦!外婆就狂扯塑膠袋,但是因為塑膠袋的品質很好,竟然扯不破!!!救命啊!最後不知道是怎麼的終於把塑膠袋弄開了 =_= 外婆說「那件事情之後我就有陰影,所以我看到小孩子在玩,把塑膠袋套頭上的時候我都很害怕。」「外婆,還好你沒死,不然很丟臉,塑膠袋套頭上,人家會以為你是自殺的,新聞標題寫『移民疑似生活困難想不開』,被認識的人看到你會被笑死 XD」「真的,還好沒死,哈哈哈哈哈」

有一次,從租屋處下樓出來丟垃圾的時候,把門開著,想說丟完垃圾就回去,結果沒想到就在人在外面的這短短的時間裡,風從房內的窗戶吹來,把門給甩上了,外婆人就被鎖在外面進不去。雖然房東就住在一樓,但是外婆並不想去跟房東拿鑰匙開門,「因為我每次有事去找他,他都會趁機偷抓我的屁股 =_= 我很生氣,寧願坐在外面吹風也不要去跟他拿鑰匙!」房東是個老米,死老不修。結果我外婆就這樣坐在外面,也不知道要幹麻,就一個人在路邊發呆,一直作到晚上,鄰居的華人回來的,看到外婆「阿你怎麼坐在外面?」「我出來丟垃圾,結果門被風關上了進不去」「那怎麼不去跟房東拿鑰匙?」「不想 = =」結果鄰居幫外婆去拿鑰匙,最後外婆才進的了家門 XD

還有一次,又是出來丟垃圾的時候,門又給風關上啦~!這次外婆一樣不想去找房東拿鑰匙,因為不想被摸屁股 XD 但是這次不一樣的是:外婆正在燒開水 XD 如果不趕快進去屋裡把火關掉,整棟房子搞不好燒光光。那怎麼辦捏?外婆住在四樓,想到二樓有一個認識的鄰居,是一對華人夫妻,就跑去三樓敲門,老公出門去了,只剩下老婆在家「那個,你家能不能讓我過一下?我門給風關上了,我想要從窗戶外面爬逃生梯上去」「不行啦!我老公如果知道我讓別人進家門他會打我耶!」「拜託你啦!我正在燒開水,如果進不去的話,整棟房子都要燒掉耶!」結果對方才讓外婆進去,外婆沿著外面逃生梯往上爬,爬到四樓,但是靠著逃生梯的窗戶是鎖著的,開著的是稍微有點距離的另外一扇窗戶(風也就是從這個窗戶灌進來把門甩上的),那個有窗戶有段距離「年輕的時候比較不怕死,我就沿著牆壁邊緣爬過去」沒想到外婆竟然就扮演蜘蛛人了。因為很危險,所以外婆花了很久的時間才爬過去,結果爬到一半的時候,被附近的米國人看到了,以為我外婆是小偷,正在從逃生梯想要爬去窗戶偷東西,結果那個老米報警了,警察來了,找了房東,房東拿了鑰匙,開了我外婆那戶的門,等我外婆好不容易爬進窗戶的時候,警察跟房東已經在家裡面等她了 XD

感恩節到了,朋友送了她們一隻火雞,但是外婆說她不知道火雞要怎麼吃,「好大一隻,中國人又不會吃這種東西,轉送給其他華人朋友也沒有人要 =_=」最後想一想,就決定拿去送給房東。房東夫婦都在,因為外婆不會講英文,外婆就在那邊比手畫腳,說要把火雞給房東,房東大概看懂意思了,就很開心準備要接受,結果我外婆就繼續比手畫腳,作出摸自己屁股的動作,然後跟房東說「No! No! No more!」意思就是「你以後不要再摸我的屁股!」當然房東看的懂是什麼意思,沒想到房東太太也看懂了,結果現場發火,把房東先生打一頓,外婆就很開心的回家去了 XD

有一天鄰居提議要吃炸雞,說他出錢,讓外婆去買,鄰居教外婆,雞的英文叫做「七肯」,外婆記住了,就跑去買炸雞,因為家裡到賣炸雞的距離很遠,外婆走了很久才到,結果走到的時候,已經忘記雞的英文是什麼了「糾竟是七肯還是八肯呢?」想了很久,還是想不起來,外婆最後決定應該是「八肯」,然後就跟店員「八肯、八肯」了很久。當然,不管外婆再怎麼八肯,店員也不會把雞賣給她。外婆只好再走很遠的路回家,再問一次雞的英文到底是七肯還是八肯,然後再跑去買,結果第二次到店裡面的時候,忘記兩人份的「吐」怎麼講了,不過這個比較簡單,後來外婆比了兩根手指,店員:「吐?」外婆:「對!對!對!吐!吐!吐!吐!」就解決這個問題了 XD

有一天走在百貨公司附近的時候,看到兩個米國年輕情侶正在接吻,外婆看到覺得很震驚!因為在台灣不可能有這種事情啊!那個時候民風保守,外婆從來沒見過這件事情,就看傻了眼,雖然眼睛看傻了,腳還在一樣在走路,但是就沒在看路,結果竟然一腳踩進清潔工留在走廊上的水桶!踩進去就算了,外婆想要把腳抽出來,但是卻沒成功,還被水桶絆到!結果整個人一直往前撲跌,最後一張臉直接撞在百貨公司的玻璃上!轟隆的好大一聲!外婆因為很痛,無法痛談,臉貼著玻璃,百貨公司裡面的人因為巨響通通看出來了,看到外婆的臉黏在玻璃上,一隻腳還在水桶裡 XD 外婆爬了起來,沒事一樣的繼續慢慢散步。我:「你沒有快點逃走嗎?很丟臉耶 XD」「有什麼好丟臉的 A_A 美國人又不認得我的臉 A_A」

2010年3月4日 星期四

Multiarray and View

最近有個東西需要用到一個四維陣列,其實本來是用四層的 hash table 下去做的,當然 hash 是不慢,但是因為取值的次數太頻繁,在跑過 profiling 之後確定這邊是一個瓶頸,所以嘗試要重構這個部分。如果為了效能考量,最好的作法就是用一個四維陣列來作。

因為本來 的 code 是有類似 indexed-view 的用法,就是我要固定某一個或是某兩個維度,然後把剩下的維度當作陣列來迭代。之前的 code 在這邊是去迭代那些 hash table,然後把需要的值取出來放到另外一個 hash table 裡面去,所以也不是真的 view,而是重新蒐集值。很慢,但是這份 code 最早是只有兩個維度的,所以當初設計的人這樣做也很合理,只是隨著時間過去,需求的追加,這部分才越來越複雜。

為了效能上的考量,我在考慮這個部分是不是不該重新串值,而是直接使用 view 的手法來取得需要的值,想到這個需求時,我開始思考我要什麼東西,這個過程在我的腦海裡面一直改版。

第一個版本是我要設計一個四維陣列類別,然後另外設計四個 view 類別,分別針對四個維度來 view 那個四維陣列。當然,可想而知,這四個 view 類別的實作會很相似,所以我是不是應該把這四個設計成一個?

所以第二個版本是我要設計一個四維陣列,還有一個 view 類別,接一個參數,用來 view 那個四維陣列,而參數可以指定我要固定的那一個維度。但是如果我想要固定兩個維度怎麼辦?我是不是應該讓這個 view class 可以套用在自己身上,也就是 templating 這個 view class?但是一旦這樣作,view 的維度就不一定是三維,而有可能是一維、二維。所以我應該用個 recursive class template 手法來做這件事?

那第三個版本就是我要設計一個四維陣列,還有一個 resursive class template 的 view 類別,用來 view 我的陣列,還有我的陣列的 view,還有 view 的 view。這樣我幹嘛不把陣列也設計成 recursive class template?這樣以後還可以重複使用,那為了重複使用,我也應該把這個陣列的型別也設計成 template。

所以第四個版本是我 要設計一個 resursive class template 可以用來定義任意維度的陣列,還有一個 view 可以用來 view 任意維度的陣列或是 view(只要我的陣列跟 view 提供相同的 interface)。這樣的話,那我幹嘛不直接用 boost::multiarray 呢?這樣我只需要設計 view 的部份就可以了。ㄟ……等等,如果我是 multiarray 的作者,我應該會想提供 view 的功能,不知道 boost::multiarray 有沒有 view 可以用?查一下好了……嗯……有。

第五個版本:

#include <boost/multi_array.hpp>

心得是那些偉大的心智真的看的很遠很遠。當我想到這個可能性的時候,他們已經做完了。

2010年2月28日 星期日

物理學家在大強子碰撞機中找到反重力物質

(法新社日內瓦)歐洲科學家公佈,去年 11 月 21 日啟動的大強子對撞機,在碰撞之後找到了許多理論中預測存在但是以前沒被觀察到的粒子,其中之一就是「反重力粒子」。

研究中心主任霍爾(Rolf Heuer)表示:「這是最令人興奮的成就之一!人類的運輸方式將從此改變!」一般帶有質量的物質都具有互相吸引的力,稱作「萬有引力」,而反引力物質具有負的質量,根據紐頓提出的萬有引力公式 F = GMm/r^2,這個反引力物質將會與其他物質互斥。「也就是說我們可以利用他來飛行」哈佛大學理論物理學系教授約翰賀蘭(John Holland)表示。

賀蘭表示:「順利的話,利用反重力飛行的技術將在三十年之內成熟。唯一的問題是,我們沒辦法證明這個成果的真實性,因為唯一的那一顆反重力粒子,因為跟重力互斥的關係,已經飛出地球了。根據我們的計算,他現在正越過冥王星的軌道。」

2010年1月21日 星期四

Valgrind Tutorial

有個老笑話說「99% 難纏的 C++ bug 都是來自於動態記憶體配置,剩下那 1% 還是來自動態記憶體配置」。很多程式設計師應該都遇過 access violation 或是 segmentation fault 這樣的記憶體錯誤。這中間絕大部分都是來自於指標的操作不當。要嘛你根本沒去 new 記憶體就直接拿來用,要嘛 new 了沒有 delete,要嘛沒有 newdelete,不然就是 delete 兩次,又或是有 new 也有 delete,但是忘記了又拿來用。雖然每一本 C/C++ 教科書都會耳提面命的、像是催眠一般的不斷喊叫「有 new 就要 delete!」但是程式設計師也是人,難免會犯錯,而且有的時候不是我們故意忘記,而是某些記憶體的所有權錯綜複雜,難以追蹤。一條路是不要使用動態記憶體,但是 C/C++ 不用動態記憶體的話,其實也做不了什麼事情。於是 C/C++ 程式設計師只好每次 new 了記憶體,都得另外消耗幾個腦細胞去記憶這些記憶體什麼時候該被放掉,結果這幾個腦細胞不小心掛啦,程式設計師也忘記要釋放這些記憶體了。而當記憶體錯誤發生的時候,程式設計師就得浪費更多的腦細胞去進行除錯的工作。日復一日的在記憶體錯誤的無間道裡面輪迴。

其實程式設計師的日子可以不用過的這麼苦。除了「眼睛用力看」以外,我們的生命還可以過的輕鬆一點。而 Valgrind 就是這樣一個工具,可以幫助我們抓出程式碼裡面關於動態記憶體的錯誤。

這篇文章會用一個簡單的範例程式,介紹 Valgrind 的用法,一步一步的,一次除去一個記憶體錯誤。而因為 Valgrind 用起來很簡單,所以文章內容也不會多,所以看完大概也學不到太多東西,但是話說回來,simple is good,Valgrind 的簡單就是他的優點,像我這樣腦損傷的人也可以順利使用。

因為 Valgrind 其實已經是很普遍的工具了,大部分的 linux dist 都內含有 Valgrind,所以這邊就跳過安裝 Valgrind 的過程,直接開始使用它。如果你發現你的機器上面沒有 Valgrind 的話,請自己另外先安裝起來。那使用 Windows 的使用者請注意到這裡,因為 Valgrind 沒有支援 Windows,出口在左手邊。

下面這個短短的程式就是我們要用來示範用的程式,裡面包含很多錯誤,而且寫的很蠢,但是因為這只是個範例,所以我們就不要太計較了。

 1 #include  <iostream>
 2 
 3 int main(int argc, char *argv[])
 4 {
 5     // new an array to store the random value
 6     int *a = new int[10] ;
 7     for ( size_t i=1; i<=10; ++i ) {
 8         a[i] = rand() ;
 9     }
10 
11     // calculate the sum the those number
12     int sum ;
13     for ( size_t i=1; i<=10; ++i ) {
14         sum += a[i] ;
15     }
16 
17     // output the result
18     std::cout << sum << std::endl ;
19 }


眼尖的人可能已經看到所有的錯誤了,那太好啦,你也可以先把這個錯誤修好再繼續下一步。下一步就是要編譯這個原始檔並且執行它,編譯的時候記得加上 -g 產生除錯訊息,這樣 valgrind 輸出的報告會給出錯誤的行號,方便我們修正程式碼。

[yoco_xiao@yoco yoco_xiao/test]$ g++ -g test.cc
[yoco_xiao@yoco yoco_xiao/test]$


現在編譯完成之後,就可以使用 Valgrind 來進行除錯的部份。Valgrind 本質上是一個 virtual machine,工作原理就是模擬一個機器來執行你的程式碼,並且監控每一行程式,如果這段碼跟記憶體的使用有關,valgrind 就會對他進行檢錯,並且在有錯誤的時候回報出來。使用 Valgrind 的方法很簡單,你不需要重新 compile,不需要定義什麼新的 macro,不需要重新 link 額外的函式庫,你只要直接使用 Valgrind 去執行你的程式。

[yoco_xiao@yoco yoco_xiao/test]$ valgrind --leak-check=full --log-file=vglog ./a.out
[yoco_xiao@yoco yoco_xiao/test]$ 


這邊 Valgrind 接了兩個參數,一個是 --leak-check=full,這是用來告訴 Valgrind 要進行完整的 memory leak 檢查,valgrind 看到這個參數的話,就會 backtrace memory leak 發生的點,有這些資訊我們比較好進行除錯。如果沒有這個參數,Valgrind 就只會輸出 memory leak 的 summary(總共漏失了多少記憶體),不會有詳細的報告。

另外一個參數是 --log-file=vglog,這是讓 Valgrind 把結果輸出到我們指定的檔案,以這個例子來說,我就把報告輸出到 vglog 這個檔案。如果不給這個參數,Valgrind 預設會把報告輸出到螢幕上。我比較推薦輸出到檔案,一來是 log 可能很長,在螢幕上可能不容易看,二來是 vim 有支援 Valgrind 的 syntax highlight,閱讀起來比較清楚。關於 Valgrind 完整的參數設定說明可以參考這邊

下面就是 Valgrind 輸出的報告。

 1 ==12723== Memcheck, a memory error detector
 2 ==12723== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
 3 ==12723== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
 4 ==12723== Command: a.out
 5 ==12723== Parent PID: 12641
 6 ==12723== 
 7 ==12723== Invalid write of size 4
 8 ==12723==    at 0x400A2B: main (test.cc:8)
 9 ==12723==  Address 0x4a3f068 is 0 bytes after a block of size 40 alloc'd
10 ==12723==    at 0x4905A9D: operator new[](unsigned long) (vg_replace_malloc.c:264)
11 ==12723==    by 0x400A00: main (test.cc:6)
12 ==12723== 
13 ==12723== Invalid read of size 4
14 ==12723==    at 0x400A56: main (test.cc:14)
15 ==12723==  Address 0x4a3f068 is 0 bytes after a block of size 40 alloc'd
16 ==12723==    at 0x4905A9D: operator new[](unsigned long) (vg_replace_malloc.c:264)
17 ==12723==    by 0x400A00: main (test.cc:6)
18 ==12723== 
19 ==12723== Conditional jump or move depends on uninitialised value(s)
20 ==12723==    at 0x3744B81589: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
21 ==12723==    by 0x3744B816F8: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
22 ==12723==    by 0x3744B86F3F: std::ostream::operator<<(long) (in /usr/lib64/libstdc++.so.6.0.3)
23 ==12723==    by 0x400A74: main (test.cc:18)
24 ==12723== 
25 ==12723== Use of uninitialised value of size 8
26 ==12723==    at 0x3744B74554: ??? (in /usr/lib64/libstdc++.so.6.0.3)
27 ==12723==    by 0x3744B81597: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
28 ==12723==    by 0x3744B816F8: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
29 ==12723==    by 0x3744B86F3F: std::ostream::operator<<(long) (in /usr/lib64/libstdc++.so.6.0.3)
30 ==12723==    by 0x400A74: main (test.cc:18)
31 ==12723== 
32 ==12723== Conditional jump or move depends on uninitialised value(s)
33 ==12723==    at 0x3744B74565: ??? (in /usr/lib64/libstdc++.so.6.0.3)
34 ==12723==    by 0x3744B81597: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
35 ==12723==    by 0x3744B816F8: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
36 ==12723==    by 0x3744B86F3F: std::ostream::operator<<(long) (in /usr/lib64/libstdc++.so.6.0.3)
37 ==12723==    by 0x400A74: main (test.cc:18)
38 ==12723== 
39 ==12723== 
40 ==12723== HEAP SUMMARY:
41 ==12723==     in use at exit: 40 bytes in 1 blocks
42 ==12723==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
43 ==12723== 
44 ==12723== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
45 ==12723==    at 0x4905A9D: operator new[](unsigned long) (vg_replace_malloc.c:264)
46 ==12723==    by 0x400A00: main (test.cc:6)
47 ==12723== 
48 ==12723== LEAK SUMMARY:
49 ==12723==    definitely lost: 40 bytes in 1 blocks
50 ==12723==    indirectly lost: 0 bytes in 0 blocks
51 ==12723==      possibly lost: 0 bytes in 0 blocks
52 ==12723==    still reachable: 0 bytes in 0 blocks
53 ==12723==         suppressed: 0 bytes in 0 blocks
54 ==12723== 
55 ==12723== For counts of detected and suppressed errors, rerun with: -v
56 ==12723== Use --track-origins=yes to see where uninitialised values come from
57 ==12723== ERROR SUMMARY: 22 errors from 6 contexts (suppressed: 4 from 4)


很長一串,很煩,但是沒關係,很多東西可以跳過,比方說開頭那幾行摘要,不用鳥他,還有每一行前面的 ==12723== 這是 process id,現在也不用鳥他,等你有多執行緒的時候再來鳥他。我們一個一個看,一次對付一個。就從第一個開始,讓我們看到第 7 行。

 7 ==12723== Invalid write of size 4
 8 ==12723==    at 0x400A2B: main (test.cc:8)
 9 ==12723==  Address 0x4a3f068 is 0 bytes after a block of size 40 alloc'd
10 ==12723==    at 0x4905A9D: operator new[](unsigned long) (vg_replace_malloc.c:264)
11 ==12723==    by 0x400A00: main (test.cc:6)


Valgrind 跟我們說我們有一個非法的寫入,他還很佛心的告訴我們這個非法的寫入是在 test.cc 這個檔案的第 8 行。Valgrind 還知道我們在第 6 行配置了一塊大小是 40 個 byte 的記憶體,而這個非法的寫入的位置,剛剛好超過了這塊記憶體 0 個 byte!(超過 0 byte 不是沒有超過,而是剛剛好超過。)那我們就去看 test.cc 的第 8 行……

 6     int *a = new int[10] ;
 7     for ( size_t i=1; i<=10; ++i ) {
 8         a[i] = rand() ;
 9     }


這個錯誤實在太明顯啦,連我都覺得不好意思了。就是 off by one 啦。大小為 n 的陣列,其索引範圍是 0~n-1,不是 1~n。不要笑,很多人幫字元陣列結尾的時候就是會寫 str[len] = '\0' ; 我老實承認很多人其實就是我自己。把它修好吧。

 6     int *a = new int[10] ;
 7     for ( size_t i=0; i<10; ++i ) {
 8         a[i] = rand() ;
 9     }


現在讓我們把修好的 code 重新編譯執行一次……

[yoco_xiao@yoco yoco_xiao/test]$ g++ -g test.cc
[yoco_xiao@yoco yoco_xiao/test]$ valgrind --leak-check=full --log-file=vglog ./a.out


 1 ==12723== Memcheck, a memory error detector
 2 ==12723== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
 3 ==12723== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
 4 ==12723== Command: a.out
 5 ==12723== Parent PID: 12641
 6 ==12723== 
13 ==12723== Invalid read of size 4
14 ==12723==    at 0x400A56: main (test.cc:14)
15 ==12723==  Address 0x4a3f068 is 0 bytes after a block of size 40 alloc'd
16 ==12723==    at 0x4905A9D: operator new[](unsigned long) (vg_replace_malloc.c:264)
17 ==12723==    by 0x400A00: main (test.cc:6)
18 ==12723== 
19 ==12723== Conditional jump or move depends on uninitialised value(s)
20 ==12723==    at 0x3744B81589: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
21 ==12723==    by 0x3744B816F8: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
22 ==12723==    by 0x3744B86F3F: std::ostream::operator<<(long) (in /usr/lib64/libstdc++.so.6.0.3)
23 ==12723==    by 0x400A74: main (test.cc:18)
24 ==12723== 
25 ==12723== Use of uninitialised value of size 8
26 ==12723==    at 0x3744B74554: ??? (in /usr/lib64/libstdc++.so.6.0.3)
27 ==12723==    by 0x3744B81597: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
28 ==12723==    by 0x3744B816F8: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
29 ==12723==    by 0x3744B86F3F: std::ostream::operator<<(long) (in /usr/lib64/libstdc++.so.6.0.3)
30 ==12723==    by 0x400A74: main (test.cc:18)
31 ==12723== 
32 ==12723== Conditional jump or move depends on uninitialised value(s)
33 ==12723==    at 0x3744B74565: ??? (in /usr/lib64/libstdc++.so.6.0.3)
34 ==12723==    by 0x3744B81597: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
35 ==12723==    by 0x3744B816F8: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
36 ==12723==    by 0x3744B86F3F: std::ostream::operator<<(long) (in /usr/lib64/libstdc++.so.6.0.3)
37 ==12723==    by 0x400A74: main (test.cc:18)
38 ==12723== 
39 ==12723== 
40 ==12723== HEAP SUMMARY:
41 ==12723==     in use at exit: 40 bytes in 1 blocks
42 ==12723==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
43 ==12723== 
44 ==12723== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
45 ==12723==    at 0x4905A9D: operator new[](unsigned long) (vg_replace_malloc.c:264)
46 ==12723==    by 0x400A00: main (test.cc:6)
47 ==12723== 
48 ==12723== LEAK SUMMARY:
49 ==12723==    definitely lost: 40 bytes in 1 blocks
50 ==12723==    indirectly lost: 0 bytes in 0 blocks
51 ==12723==      possibly lost: 0 bytes in 0 blocks
52 ==12723==    still reachable: 0 bytes in 0 blocks
53 ==12723==         suppressed: 0 bytes in 0 blocks
54 ==12723== 
55 ==12723== For counts of detected and suppressed errors, rerun with: -v
56 ==12723== Use --track-origins=yes to see where uninitialised values come from
57 ==12723== ERROR SUMMARY: 22 errors from 6 contexts (suppressed: 4 from 4)


Invalid write 那行錯誤果然不見了!這個錯誤被我們修好啦!那有人可能會問:「為什麼這次執行的 process id 跟之前的一樣呢?」太神奇了!原因當然是因為我根本沒有去跑阿,我直接把之前那個 log 檔拿來改一改而已,你看那個不連續的行號就漏餡了。但是真的修好了啦,你們要自己動手做看看就知道了,不能只依賴我。接下來要看第二個錯誤……

13 ==12723== Invalid read of size 4
14 ==12723==    at 0x400A56: main (test.cc:14)
15 ==12723==  Address 0x4a3f068 is 0 bytes after a block of size 40 alloc'd
16 ==12723==    at 0x4905A9D: operator new[](unsigned long) (vg_replace_malloc.c:264)
17 ==12723==    by 0x400A00: main (test.cc:6)


Valgrind 說 test.cc 的第 14 行有一個 Invalid read。這個錯誤也很明顯,跟上一個一樣是 off by one,請自己嘗試把它修好,然後重新編譯,再用 Valgrind 執行一次……

[yoco_xiao@yoco yoco_xiao/test]$ g++ -g test.cc
[yoco_xiao@yoco yoco_xiao/test]$ valgrind --leak-check=full --log-file=vglog ./a.out


 1 ==12723== Memcheck, a memory error detector
 2 ==12723== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
 3 ==12723== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
 4 ==12723== Command: a.out
 5 ==12723== Parent PID: 12641
 6 ==12723== 
19 ==12723== Conditional jump or move depends on uninitialised value(s)
20 ==12723==    at 0x3744B81589: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
21 ==12723==    by 0x3744B816F8: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
22 ==12723==    by 0x3744B86F3F: std::ostream::operator<<(long) (in /usr/lib64/libstdc++.so.6.0.3)
23 ==12723==    by 0x400A74: main (test.cc:18)
24 ==12723== 
25 ==12723== Use of uninitialised value of size 8
26 ==12723==    at 0x3744B74554: ??? (in /usr/lib64/libstdc++.so.6.0.3)
27 ==12723==    by 0x3744B81597: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
28 ==12723==    by 0x3744B816F8: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
29 ==12723==    by 0x3744B86F3F: std::ostream::operator<<(long) (in /usr/lib64/libstdc++.so.6.0.3)
30 ==12723==    by 0x400A74: main (test.cc:18)
31 ==12723== 
32 ==12723== Conditional jump or move depends on uninitialised value(s)
33 ==12723==    at 0x3744B74565: ??? (in /usr/lib64/libstdc++.so.6.0.3)
34 ==12723==    by 0x3744B81597: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
35 ==12723==    by 0x3744B816F8: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.3)
36 ==12723==    by 0x3744B86F3F: std::ostream::operator<<(long) (in /usr/lib64/libstdc++.so.6.0.3)
37 ==12723==    by 0x400A74: main (test.cc:18)
38 ==12723== 
39 ==12723== 
40 ==12723== HEAP SUMMARY:
41 ==12723==     in use at exit: 40 bytes in 1 blocks
42 ==12723==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
43 ==12723== 
44 ==12723== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
45 ==12723==    at 0x4905A9D: operator new[](unsigned long) (vg_replace_malloc.c:264)
46 ==12723==    by 0x400A00: main (test.cc:6)
47 ==12723== 
48 ==12723== LEAK SUMMARY:
49 ==12723==    definitely lost: 40 bytes in 1 blocks
50 ==12723==    indirectly lost: 0 bytes in 0 blocks
51 ==12723==      possibly lost: 0 bytes in 0 blocks
52 ==12723==    still reachable: 0 bytes in 0 blocks
53 ==12723==         suppressed: 0 bytes in 0 blocks
54 ==12723== 
55 ==12723== For counts of detected and suppressed errors, rerun with: -v
56 ==12723== Use --track-origins=yes to see where uninitialised values come from
57 ==12723== ERROR SUMMARY: 22 errors from 6 contexts (suppressed: 4 from 4)


當然,這次的 process id 也是一樣的。

我們還剩下好幾個錯誤訊息,接下來看第三個:Conditional jump or move depends on uninitialised value(s) 在 test.cc 第 18 行。

18     std::cout << sum << std::endl ;


看不出來為什麼錯,也看不是很懂那個錯誤訊息,不知道他在講啥,不過沒關係,看的懂 "uninitialised value" 就好了。重點就是有人沒有初始化就被拿來用了。是誰勒,這邊的話當然就是 sum。可以看看 log 的最後,他還有告訴你如果打開選項 --trace-originals=yes 的話就可以 dump 出當初這塊記憶體是哪裡來的。不過因為這個例子很簡單,我們知道 sum 這個變數是哪裡來的。


12     int sum ;


把它修好……

12     int sum = 0 ;


重新編譯再執行……

[yoco_xiao@yoco yoco_xiao/test]$ g++ -g test.cc
[yoco_xiao@yoco yoco_xiao/test]$ valgrind --leak-check=full --log-file=vglog ./a.out


 1 ==12174== Memcheck, a memory error detector
 2 ==12174== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
 3 ==12174== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
 4 ==12174== Command: a.out
 5 ==12174== Parent PID: 12091
 6 ==12174== 
 7 ==12174== 
 8 ==12174== HEAP SUMMARY:
 9 ==12174==     in use at exit: 40 bytes in 1 blocks
10 ==12174==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
11 ==12174== 
12 ==12174== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
13 ==12174==    at 0x4905A9D: operator new[](unsigned long) (vg_replace_malloc.c:264)
14 ==12174==    by 0x400A00: main (test.cc:6)
15 ==12174== 
16 ==12174== LEAK SUMMARY:
17 ==12174==    definitely lost: 40 bytes in 1 blocks
18 ==12174==    indirectly lost: 0 bytes in 0 blocks
19 ==12174==      possibly lost: 0 bytes in 0 blocks
20 ==12174==    still reachable: 0 bytes in 0 blocks
21 ==12174==         suppressed: 0 bytes in 0 blocks
22 ==12174== 
23 ==12174== For counts of detected and suppressed errors, rerun with: -v
24 ==12174== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)


來看這個 log,剛剛一堆錯誤都不見了,因為那些錯誤都因為來自於那個沒有初始化的變數,所以修好一個就少掉一堆錯誤訊息。看起來只剩下一個錯誤啦 40 bytes in 1 blocks are definitely lost in loss record 1 of 1。重點來啦!這是一個沒有被釋放的記憶體!這塊記憶體是在 test.cc 的第 6 行被配置的!沒有初始化的變數所造成的錯誤都可以很快被發現,因為他通常會有錯誤的結果,比方說如果你們剛剛真的有下去跑的話,就會發現每次輸出的總和值都很明顯是錯的。而且沒有初始化的變數通常編譯器都會給予警告。但是沒有被釋放的記憶體不有什麼立即的錯誤,編譯器也會一聲不吭的就放行。所以我們才需要記憶體除錯工具來幫助我們抓到這類 bug。現在讓我們動手來釋放他……

13     for ( size_t i=1; i<=10; ++i ) {
14         sum += a[i] ;
15     }
16 
17     // output the result
18     std::cout << sum << std::endl ;


13     for ( size_t i=0; i<10; ++i ) {
14         sum += a[i] ;
15     }
16     
17     delete a ;
18 
19     // output the result
20     std::cout << sum << std::endl ;


重新編譯再執行……

[yoco_xiao@yoco yoco_xiao/test]$ g++ -g test.cc
[yoco_xiao@yoco yoco_xiao/test]$ valgrind --leak-check=full --log-file=vglog ./a.out


 7 ==25463== Mismatched free() / delete / delete []
 8 ==25463==    at 0x4906174: operator delete(void*) (vg_replace_malloc.c:346)
 9 ==25463==    by 0x400B07: main (test.cc:17)
10 ==25463==  Address 0x4a3f040 is 0 bytes inside a block of size 40 alloc'd
11 ==25463==    at 0x4905A9D: operator new[](unsigned long) (vg_replace_malloc.c:264)
12 ==25463==    by 0x400A90: main (test.cc:6)


Memory leak 的錯誤不見了,但是又多了一個本來沒看過的錯誤,Mismatched free() / delete / delete []。Valgrind 說這個錯誤發生在 test.cc 的第 17 行,而且這個記憶體是在第 6 行被配置的,讓我們看一下第 17 行跟第 6 行是啥。

 6     int *a = new int[10] ;


17     delete a ;


忘記了,new[] 的記憶體要用 delete[] 來釋放,不能單純只用 delete,C++ 標準說這樣的用法會導致未定義的行為,就是在不同的編譯器上面會有不同的結果,比方說:雖然這塊記憶體被釋放了,但是可能只有第一個物件呼叫了建構子,剩下的都沒有,而這不會是我們希望的結果。讓我們修好他,然後重新編譯再執行……

17     delete[] a ;


[yoco_xiao@yoco yoco_xiao/test]$ g++ -g test.cc
[yoco_xiao@yoco yoco_xiao/test]$ valgrind --leak-check=full --log-file=vglog ./a.out


 1 ==26986== Memcheck, a memory error detector
 2 ==26986== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
 3 ==26986== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
 4 ==26986== Command: a.out
 5 ==26986== Parent PID: 26904
 6 ==26986== 
 7 ==26986== 
 8 ==26986== HEAP SUMMARY:
 9 ==26986==     in use at exit: 0 bytes in 0 blocks
10 ==26986==   total heap usage: 1 allocs, 1 frees, 40 bytes allocated
11 ==26986== 
12 ==26986== All heap blocks were freed -- no leaks are possible
13 ==26986== 
14 ==26986== For counts of detected and suppressed errors, rerun with: -v
15 ==26986== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)


總算!終於功德圓滿修成正果啦~全部的錯誤都不見啦~可喜可賀!可喜可賀!才怪。

其實呢,記憶體偵錯只是 Valgrind 的其中一個功能,Valgrind 是一個 virtual machine,可以透過掛上不同的 tool 來執行,目前為止我們用的都是 Valgrind 內建的一個叫做 memcheck 的 tool,Valgrind 還有像是 callgrind(用來做 profiling),cachegrind(用來模擬 CPU cache hit/miss)的功能,這個以後再說,當然,等不及的人直接 Google 就有一堆教學啦。

有錯不要罵我。