(法新社日內瓦)歐洲科學家公佈,去年 11 月 21 日啟動的大強子對撞機,在碰撞之後找到了許多理論中預測存在但是以前沒被觀察到的粒子,其中之一就是「反重力粒子」。
研究中心主任霍爾(Rolf Heuer)表示:「這是最令人興奮的成就之一!人類的運輸方式將從此改變!」一般帶有質量的物質都具有互相吸引的力,稱作「萬有引力」,而反引力物質具有負的質量,根據紐頓提出的萬有引力公式 F = GMm/r^2,這個反引力物質將會與其他物質互斥。「也就是說我們可以利用他來飛行」哈佛大學理論物理學系教授約翰賀蘭(John Holland)表示。
賀蘭表示:「順利的話,利用反重力飛行的技術將在三十年之內成熟。唯一的問題是,我們沒辦法證明這個成果的真實性,因為唯一的那一顆反重力粒子,因為跟重力互斥的關係,已經飛出地球了。根據我們的計算,他現在正越過冥王星的軌道。」
2010年2月28日 星期日
2010年1月21日 星期四
Valgrind Tutorial
有個老笑話說「99% 難纏的 C++ bug 都是來自於動態記憶體配置,剩下那 1% 還是來自動態記憶體配置」。很多程式設計師應該都遇過 access violation 或是 segmentation fault 這樣的記憶體錯誤。這中間絕大部分都是來自於指標的操作不當。要嘛你根本沒去
其實程式設計師的日子可以不用過的這麼苦。除了「眼睛用力看」以外,我們的生命還可以過的輕鬆一點。而 Valgrind 就是這樣一個工具,可以幫助我們抓出程式碼裡面關於動態記憶體的錯誤。
這篇文章會用一個簡單的範例程式,介紹 Valgrind 的用法,一步一步的,一次除去一個記憶體錯誤。而因為 Valgrind 用起來很簡單,所以文章內容也不會多,所以看完大概也學不到太多東西,但是話說回來,simple is good,Valgrind 的簡單就是他的優點,像我這樣腦損傷的人也可以順利使用。
因為 Valgrind 其實已經是很普遍的工具了,大部分的 linux dist 都內含有 Valgrind,所以這邊就跳過安裝 Valgrind 的過程,直接開始使用它。如果你發現你的機器上面沒有 Valgrind 的話,請自己另外先安裝起來。那使用 Windows 的使用者請注意到這裡,因為 Valgrind 沒有支援 Windows,出口在左手邊。
下面這個短短的程式就是我們要用來示範用的程式,裡面包含很多錯誤,而且寫的很蠢,但是因為這只是個範例,所以我們就不要太計較了。
眼尖的人可能已經看到所有的錯誤了,那太好啦,你也可以先把這個錯誤修好再繼續下一步。下一步就是要編譯這個原始檔並且執行它,編譯的時候記得加上
現在編譯完成之後,就可以使用 Valgrind 來進行除錯的部份。Valgrind 本質上是一個 virtual machine,工作原理就是模擬一個機器來執行你的程式碼,並且監控每一行程式,如果這段碼跟記憶體的使用有關,valgrind 就會對他進行檢錯,並且在有錯誤的時候回報出來。使用 Valgrind 的方法很簡單,你不需要重新 compile,不需要定義什麼新的 macro,不需要重新 link 額外的函式庫,你只要直接使用 Valgrind 去執行你的程式。
這邊 Valgrind 接了兩個參數,一個是
另外一個參數是
下面就是 Valgrind 輸出的報告。
很長一串,很煩,但是沒關係,很多東西可以跳過,比方說開頭那幾行摘要,不用鳥他,還有每一行前面的
Valgrind 跟我們說我們有一個非法的寫入,他還很佛心的告訴我們這個非法的寫入是在 test.cc 這個檔案的第 8 行。Valgrind 還知道我們在第 6 行配置了一塊大小是 40 個 byte 的記憶體,而這個非法的寫入的位置,剛剛好超過了這塊記憶體 0 個 byte!(超過 0 byte 不是沒有超過,而是剛剛好超過。)那我們就去看 test.cc 的第 8 行……
這個錯誤實在太明顯啦,連我都覺得不好意思了。就是 off by one 啦。大小為 n 的陣列,其索引範圍是 0~n-1,不是 1~n。不要笑,很多人幫字元陣列結尾的時候就是會寫
現在讓我們把修好的 code 重新編譯執行一次……
Invalid write 那行錯誤果然不見了!這個錯誤被我們修好啦!那有人可能會問:「為什麼這次執行的 process id 跟之前的一樣呢?」太神奇了!原因當然是因為我根本沒有去跑阿,我直接把之前那個 log 檔拿來改一改而已,你看那個不連續的行號就漏餡了。但是真的修好了啦,你們要自己動手做看看就知道了,不能只依賴我。接下來要看第二個錯誤……
Valgrind 說 test.cc 的第 14 行有一個 Invalid read。這個錯誤也很明顯,跟上一個一樣是 off by one,請自己嘗試把它修好,然後重新編譯,再用 Valgrind 執行一次……
當然,這次的 process id 也是一樣的。
我們還剩下好幾個錯誤訊息,接下來看第三個:
看不出來為什麼錯,也看不是很懂那個錯誤訊息,不知道他在講啥,不過沒關係,看的懂
把它修好……
重新編譯再執行……
來看這個 log,剛剛一堆錯誤都不見了,因為那些錯誤都因為來自於那個沒有初始化的變數,所以修好一個就少掉一堆錯誤訊息。看起來只剩下一個錯誤啦
重新編譯再執行……
Memory leak 的錯誤不見了,但是又多了一個本來沒看過的錯誤,
忘記了,
總算!終於功德圓滿修成正果啦~全部的錯誤都不見啦~可喜可賀!可喜可賀!才怪。
其實呢,記憶體偵錯只是 Valgrind 的其中一個功能,Valgrind 是一個 virtual machine,可以透過掛上不同的 tool 來執行,目前為止我們用的都是 Valgrind 內建的一個叫做 memcheck 的 tool,Valgrind 還有像是 callgrind(用來做 profiling),cachegrind(用來模擬 CPU cache hit/miss)的功能,這個以後再說,當然,等不及的人直接 Google 就有一堆教學啦。
有錯不要罵我。
new 記憶體就直接拿來用,要嘛 new 了沒有 delete,要嘛沒有 new 卻 delete,不然就是 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 就有一堆教學啦。
有錯不要罵我。
2009年8月24日 星期一
建置工具阿...
最早是寫 Quick Basic 跟 Turbo C++,之後用 Visual Studio/Dev-C++/Code::Blocks,其實一直以來都不需要自己撰寫 makefile,雖然要寫也是會寫,但是可以的話還是交給工具直接作。隨著時間過去會想要控制一些建置的細節,所以想找一套好的建置工具,功能不用超強大,但是希望簡單好用。進行了一些調查...
Make,有時候實在覺得不好用,所以才會想去找別的工具。
bjam,講實話真是難學難用,除了「可以一次編出多個binary」也沒真的感覺到他有什麼特點與眾不同。重點是文件一是不足,二是不好,這真是致命傷。
SCons,有趣,跟我心裡想的方向一致。如果建置想要良好的彈性,那 script 本身的描述力最好要足夠強大方便,Make script 完全不及格,jam language 算是堪用,但是都不及 SCons 直接使用 Python 當作 script language 來用的好。
CMake,最近真是紅耶。稍微看了一下,好似不錯,但是我的心思已經偏向 SCons 啦~
回頭想了一下,其實仿造 SCons 自己打造一個建構工具也沒很難,我不需要寫 parser 什麼鬼的東西,單純寫成一個 Pyhton package 就可以簡單使用了,老實說不只是不難,而是非常簡單。
bjam 難學到爆,但是自動徵測 header file 的能力 SCons 沒有,bjam 的文件有講實作原理,其實超簡單就是用 grep 直接抓,這樣我可以輕鬆實作出這點,可以補足 SCons 不足的地方。對於現代的 C++ 來說,generic programming 很常見,寫程式的過程很多時候都是要改動 header 而不改動 source。很明顯 bjam 這個專為 boost 設計的建置工具是考慮到這點了。
以後就用自己的建置工具好了。最簡單的開頭就是...
## yocomake.py
import os
def exe ( target, source ) :
cmd = 'g++ -o a.out ' + ' '.join(source)
print cmd
os.system(cmd)
## ymake
from yocomake import exe
exe ( 'a.out', ['test.cc'] )
耶,馬上就可以用了~事情總是簡單~以後要什麼功能再慢慢加上就好!這樣好像要打比 make 跟 bjam 還有更多字喔?管他的!爽就好!
想了一下以後可能需要做的東西,再多看了 SCons 兩眼,發現 SCons 的設計還真是符合我要的 XD 那我還是直接拿來改就好了 XD
Make,有時候實在覺得不好用,所以才會想去找別的工具。
bjam,講實話真是難學難用,除了「可以一次編出多個binary」也沒真的感覺到他有什麼特點與眾不同。重點是文件一是不足,二是不好,這真是致命傷。
SCons,有趣,跟我心裡想的方向一致。如果建置想要良好的彈性,那 script 本身的描述力最好要足夠強大方便,Make script 完全不及格,jam language 算是堪用,但是都不及 SCons 直接使用 Python 當作 script language 來用的好。
CMake,最近真是紅耶。稍微看了一下,好似不錯,但是我的心思已經偏向 SCons 啦~
回頭想了一下,其實仿造 SCons 自己打造一個建構工具也沒很難,我不需要寫 parser 什麼鬼的東西,單純寫成一個 Pyhton package 就可以簡單使用了,老實說不只是不難,而是非常簡單。
bjam 難學到爆,但是自動徵測 header file 的能力 SCons 沒有,bjam 的文件有講實作原理,其實超簡單就是用 grep 直接抓,這樣我可以輕鬆實作出這點,可以補足 SCons 不足的地方。對於現代的 C++ 來說,generic programming 很常見,寫程式的過程很多時候都是要改動 header 而不改動 source。很明顯 bjam 這個專為 boost 設計的建置工具是考慮到這點了。
以後就用自己的建置工具好了。最簡單的開頭就是...
## yocomake.py
import os
def exe ( target, source ) :
cmd = 'g++ -o a.out ' + ' '.join(source)
print cmd
os.system(cmd)
## ymake
from yocomake import exe
exe ( 'a.out', ['test.cc'] )
耶,馬上就可以用了~事情總是簡單~以後要什麼功能再慢慢加上就好!這樣好像要打比 make 跟 bjam 還有更多字喔?管他的!爽就好!
想了一下以後可能需要做的東西,再多看了 SCons 兩眼,發現 SCons 的設計還真是符合我要的 XD 那我還是直接拿來改就好了 XD
2009年8月20日 星期四
路面電車
因為一段時間沒回家了,所以想說找個週末回家,於是這個週末就去火車站搭上火車前往中壢,本來還差一點趕不上這般電車的,死命的跑後來追上了。
電車開著開著,不知道為什麼覺得鐵軌兩邊的周圍的護欄都不見了,而且周圍的人也越來越多,電車的速度也變慢了,軌道也變了,變成路面電車的那種輕軌。我發現自己其實搭的是雙層的電車,雙層的路面電車,於是我走下去,想去一樓看看,結果一下去就看到司機,是俁之,然後一樓的乘客都是俁之的親戚啦,原來這是他們出遊的專車,我是不小心搭錯車搭到的。
然後就跟俁之開始聊天,俁之就一邊跟我聊天一邊開路面電車(好孩子不要學,請勿在電車行使中跟駕駛談話,當然,駕駛也請不要一邊駛車一邊跟大學同學聊天)。路面電車發展的非常完整,幾乎所有的大街小巷都有軌道,還好幾軌,真正實現 Door to Door 的軌道運輸,開電車就像是開汽車一樣方便。
後來開著開著就開到俁之台南家了,電車停在他家門口,說會在這邊吃完晚餐以後就要繼續出發。俁之說等等會駛往澎湖,問我要不要去,我說沒辦法,因為我本來是要回中壢的,來到台南已經是反方向了。
醒了,因為我並不想搭乘路面電車去澎湖 =,=
電車開著開著,不知道為什麼覺得鐵軌兩邊的周圍的護欄都不見了,而且周圍的人也越來越多,電車的速度也變慢了,軌道也變了,變成路面電車的那種輕軌。我發現自己其實搭的是雙層的電車,雙層的路面電車,於是我走下去,想去一樓看看,結果一下去就看到司機,是俁之,然後一樓的乘客都是俁之的親戚啦,原來這是他們出遊的專車,我是不小心搭錯車搭到的。
然後就跟俁之開始聊天,俁之就一邊跟我聊天一邊開路面電車(好孩子不要學,請勿在電車行使中跟駕駛談話,當然,駕駛也請不要一邊駛車一邊跟大學同學聊天)。路面電車發展的非常完整,幾乎所有的大街小巷都有軌道,還好幾軌,真正實現 Door to Door 的軌道運輸,開電車就像是開汽車一樣方便。
後來開著開著就開到俁之台南家了,電車停在他家門口,說會在這邊吃完晚餐以後就要繼續出發。俁之說等等會駛往澎湖,問我要不要去,我說沒辦法,因為我本來是要回中壢的,來到台南已經是反方向了。
醒了,因為我並不想搭乘路面電車去澎湖 =,=
2009年6月30日 星期二
C++ 沒有 split
C++ 沒有 split()。很多 script language 都有 split 這個好用的東西,但是 C++ 沒有,使用 boost::xpressive 我們可以用七行程式碼實作一個。
順帶提一下,這個函數不採用由外部傳入
std::vector<std::string> split ( const std::string &s ) {
using namespace boost::xpressive ;
sregex_token_iterator begin( s.begin(), s.end(), +_s, -1 ), end;
std::vector<std::string> v ;
std::copy( begin, end, std::back_inserter(v) );
return std::move(v) ;
}
split ( "A B C" ) ; // ["A", "B", "C"]
split ( "A BB\tCCC\n\nDDDD" ) ; // ["A", "BB", "CCC", "DDDD"]跟 Python 的 split 一樣,切割字元是三種空字元 ' ', '\t', 還有 '\n',而且連續的空白會被忽略。順帶提一下,這個函數不採用由外部傳入
std::vector<std::string>> 接收 tokens 的設計,而是用 std::move() 把結果傳出來,使用上更直覺簡單,感謝 C++0x 帶給 C++ 更強大的表述能力。
2009年5月26日 星期二
阿喵
上個禮拜二在版上看到有人說他家附近有隻流浪貓,在胡適國小附近,再問有沒有人要領養的,我看了一下照片很漂亮呢,於是就寫了信過去說想要養。當天晚上我看到的時候已經是快十點吧,我自己沒辦法從新竹過去,但是在中研院工作的學姐剛剛好在那個附近,於是學姐就幫我過去看了(一開始還說洗完澡不想出門的),學姐回來已經快要十二點了,說有看到貓,但是沒抓到,因為飼料已經被吃光了。
第二天,禮拜三,收到人家的回信了,打了電話聯絡,跟對方說我跟學姐學姐可能晚上要再過去,到時候也許會請對方幫忙找地點,於是下班之後就趕去台北囉。
到了南港捷運站,學姐就來接我過去胡適國小附近的空地,然後打了電話給原來po文的田同學(是可愛的大學生),田同學跟她的男朋友都有來,是男朋友先來的,因為那個時候田同學正在洗澡,哈。我們到的時候,剛好看到阿喵,正在一個阿姨家前面,哇~真的很漂亮~但是阿喵一看到陌生人馬上就跑了,之後也不太容易接近,我離他最近的時候不到一公尺。他們幫我介紹了那位阿姨,阿姨每天都會餵貓咪,那隻阿喵每天都會去阿姨家吃飯跟上廁所(這傢伙沒有貓沙不上廁所的),所以跟阿姨很熟,阿姨說可以幫我們抓,但是今天下午因為曾經想要抓,但是抓失敗了,所以目前阿喵警戒心很強,所以不是很好抓。阿姨跟我們說我們有空可多去那附近跟她玩,跟她熟了以後才有機會抱到她帶走,雖然說硬要抓也不是抓不到,但是這樣就算帶回家也不開心。這天晚上宣告失敗,阿姨說可以幫我們抓,於是我們把外出籃留給阿姨,請阿姨抓到的時候通知我們。真的很感謝阿姨。那天跟學姐吃過晚餐以後就回新竹了,到家已經十一點半。
之後過了兩天都沒有消息,我想說週末再去台北一趟,兩天都在那邊跟阿喵培養感情,不知道能不能抓到呢?禮拜六下午就出發前往台北,到的時候大概是五點,沒有看到阿喵,但是阿姨在,阿姨說阿喵要晚一點才會來吃飯,叫我們晚一點再來,大概七八點的時候吧,於是我跟學姐就先去吃飯了。吃玩飯之後,就到學姐家等時間,等著等著接到阿姨打來的電話了,說貓已經抓到了,於是我跟學姐就趕快過去了。
到的時候,阿姨拿著籃子在等我們,說阿喵一直叫一直叫,她很心疼,說自己開了罐頭給她吃,吃一半就把她抓起來,好像在騙她,覺得很內疚。其實阿姨在講到阿喵的時候,都可以感覺到她很希望阿喵是真的喜歡我們才跟我們走,而不是被抓走的,我覺得有點難過,因為自己好似是因為單純喜歡阿喵漂亮就來找她,如果這樣把阿喵帶走,阿姨以後就看不到阿喵了,是有點難過,但是就用力說服自己說阿喵當流浪貓也很可憐,希望被我領養以後會比較幸福。
阿姨的女兒說阿姨很喜歡喵,叫我們之後要記得記照片給她,這樣阿姨才知道阿喵過得好不好。我們道謝以後就讓阿喵跟阿姨說再見,阿姨說不要再見啦,很難過,我跟學姐就帶著阿喵走了。
去了位在附近的中研動物醫院,醫生是個很high的人,講什麼都又快又激動。檢查的結果是:女生,兩歲多,跳蚤跟耳疥蟲都有點嚴重,沒有晶片,沒有結紮。醫生沒有說有沒有寄生蟲,只說目前要處理的嚴重問題就是耳疥蟲跟跳蚤。我們買了蚤不到跟疥蟲的藥,醫生示範了一下怎麼幫貓咪擦藥跟清耳朵。我看了一下記得動作。吃過飯以後我買了貓沙跟一些用具就回新竹了。本來是想說如果貓沒有跳蚤或是傳染病的話,我就把貓放到中壢給媽媽照顧,因為媽媽也喜歡貓,而且中壢還有兩隻之前撿到的米克斯(哭哭哩跟勇者大人),這樣阿喵也有伴,就不會白天我去上班的時候她就只能一個人無聊在家。但是既然有跳蚤,我就只好先帶回新竹。
回到新竹已經很晚了,因為知道貓剛換新環境都會先躲起來,其實可以不用去抓,只要讓她知道貓沙跟食物跟水在哪裡就好,她覺得環境安全的時候自然就會出來。我把房間整理了一下,貓沙準備好,食物跟水也準備好。把阿喵抱出來之後我把她放到貓沙上面,然後是食物跟水前面,讓她知道這些東西在哪裡,之後她就可以隨便躲沒關係。我先把床底下都塞住,因為床底下她進去了我很難抓到,也很難看到,沒辦法掌握阿喵的狀況,最後她選定的地方是我的書桌下面,那邊很ok,我可以很輕鬆的找到她。一般會躲個幾天,很正常,我想我不用太擔心。
一直到我睡覺,她都沒有出來。我關燈以後躺在床上,沒多久就聽到她窸窸窣窣的爬到便盆上面上廁所,果然是美少女阿,再怎麼難忍受也不會隨便上廁所,可以不吃飯,但是上廁所很重要。上完廁所以後好似咬了兩口飼料,然後開始探險房間,我想看仔細她在玩什麼,但是她聽到我翻身的聲音,又龜回書桌下面了。然後我也睡了。
禮拜天一整天阿喵幾乎都在書桌下面,我也不太去打擾她,偶而會去摸摸她,我發現她對摸摸的反應非常好,雖然是害怕的躲在書桌下,但是一旦摸摸,她就會很配合的脖子伸長,還會翻身橋姿勢讓你摸,更誇張的是,摸一摸我收手,她竟然還會爬出來一點點繼續討摸摸(但是太出來就不會了)。就這樣,偶而摸一下摸一下,到了晚上摸著摸著竟然她就整個爬出來了,然後我停手之後,她就跑去吃飯喝水上廁所,然後開始探險房間,看樣子是完全不怕了。沒想到一天就是環境了呢,很開心。雖然很開心,但是也有傷腦筋的地方,因為禮拜天晚上她就想要上我的床睡覺 orz 但是她還沒洗澡,而且跳蚤也很多,雖然她是美少女,我也只好先拒絕她了,擋了幾次之後她就乖乖的在地上睡著了。
禮拜一一起床,幫他清了耳朵滴了藥(禮拜天也做了兩次,一天要兩次,我都上下班的時候弄),下班以後估算就是蚤不到投藥之後的48小時,可以洗澡了!太高興了!但是在這之前要先梳毛跟剪毛!她身上有好多好髒的東西,一定要先處理掉,於是開始了可怕的修羅地獄,把她的毛全部梳開,一點一點的梳,深的梳不開的就用剪刀修掉,下班時去買貓用品的時候,美容院的阿姨有教我怎麼幫貓修毛,用排梳梳阿梳,梳不開的地方就用排梳把毛跟皮膚隔開,這樣剪毛就不會傷到貓,果然是大大受用。但是梳很久以後,發現重大災難,那就是阿喵的毛很多其實不是打結也不是口香糖,而是鬼針草,她的尾巴至少有上百根的鬼針草的種子,那東西對貓毛的黏著力超強的,很多貓毛就會因為一根鬼針草結在一起,鬼針草真的很多,以我的技術,如果要剪毛可能就得趨近剃光那樣醜,所以我開始手動把鬼針草一根一根挑出來。梳毛,剪毛,挑鬼針草,這個動作大概用掉我兩個小時吧,人都快哭出來了,終於挑完了。
帶著他進去浴室,開始洗!沒想到洗澡還蠻乖的,會逃是會逃,但是就是慢慢的蛇開這樣,輕輕一撈就回來了,不會抓不會咬,所以很順利的洗好了。之後抱出來擦乾,擦乾也是很順利,因為手隔著布擦她的時候,她會覺得是在摸摸,所以很配合,還會翻身呢。但是毛實在又多又長又密,擦了超久才稍微有點乾。下一個步驟就是所有幫貓洗澡的修羅階段了:吹乾。我還沒見過貓不怕吹風機的。有的是聽到聲音就開始爆衝,有的是被風吹到就會貓爪全開跟你格鬥,安分一點的就是逃命逃到不爽反過來抓你咬你這樣。
我把阿喵放在腿上,一手一邊摸她讓她乖乖,另一首把吹風機打開,我在想我比她還要抖。令人意外的是她聽到聲音沒事耶!一邊摸她,一邊嘗試把吹風機從很遠的地方開始慢慢吹……動了……她翻身了……她覺得這樣很舒服 -_-" 這是貓嗎 -_-?就這樣,很順的吹貓,但是因為毛真的很多很密,所以吹了一個多小時才全乾,我真的快累攤了。但是事情還沒完,因為洗澡的時候發現阿喵其實還有一些結塊的毛,所以我再次進行梳毛的動作,這次弄完之後阿喵真的超漂亮的了,又漂亮又香,有家貓的感覺囉。可惜我沒相機,這時候突然覺得之前沒買真是錯了,過兩天回中壢去拿弟弟的相機來拍 :P
既然弄乾淨了,那第一件事情就是把這個又香又漂亮的美少女放到床上,哈哈~放上去之後她也很開心,捲在我的棉被上面,然後就開始睡覺了(打這篇文章的時候她也睡在枕頭上喔)。弄到這個時候,已經十二點了,也就是說我辛苦了四個多小時,終於打理完畢,以後可以開始順利的過貓貓生活啦~
然後是附註:
我花 150 買的高級釣竿型逗貓棒,這傢伙鳥都不鳥。然後買了貓草想要給她吃,但是她逃得比什麼都快,看樣子她很不喜歡貓草,阿喵真的是貓嗎……
然後名字「阿喵」,其實我還沒想到什麼名字,但是照顧她的阿姨都叫她「阿喵」,所以我就跟著叫她阿喵了,叫她會有反應喔,很聰明…
最後是很難過的事情,在版上看到有人回的文章,「我突然想到,有次我去中研動物醫院,有個媽媽帶了虎班金吉拉去看醫生,因為貓咪亂吃東西然後拉肚子,媽媽一直說她很討厭貓,問我說我要不要,她說她很想把貓丟掉,那隻貓當初是有人帶去美容丟在中研獸醫院就再也沒來帶回,後來醫生給她養的,但是後來她又不喜歡,是三個兒子說養寵物就要好好照顧牠,不知道是不是那位媽媽丟出來的,那時那媽媽有點誇張,一直囔囔要送人,很討厭貓,想要丟掉.......」雖然不一定是同一隻啦,但是虎斑的長毛貓也不是滿街跑,地點跟品種都這麼巧合,我想阿喵大概就是當初那隻吧,被兩任主人拋棄的可憐孩子。話說她現在很幸福的躺在地上睡覺。
第二天,禮拜三,收到人家的回信了,打了電話聯絡,跟對方說我跟學姐學姐可能晚上要再過去,到時候也許會請對方幫忙找地點,於是下班之後就趕去台北囉。
到了南港捷運站,學姐就來接我過去胡適國小附近的空地,然後打了電話給原來po文的田同學(是可愛的大學生),田同學跟她的男朋友都有來,是男朋友先來的,因為那個時候田同學正在洗澡,哈。我們到的時候,剛好看到阿喵,正在一個阿姨家前面,哇~真的很漂亮~但是阿喵一看到陌生人馬上就跑了,之後也不太容易接近,我離他最近的時候不到一公尺。他們幫我介紹了那位阿姨,阿姨每天都會餵貓咪,那隻阿喵每天都會去阿姨家吃飯跟上廁所(這傢伙沒有貓沙不上廁所的),所以跟阿姨很熟,阿姨說可以幫我們抓,但是今天下午因為曾經想要抓,但是抓失敗了,所以目前阿喵警戒心很強,所以不是很好抓。阿姨跟我們說我們有空可多去那附近跟她玩,跟她熟了以後才有機會抱到她帶走,雖然說硬要抓也不是抓不到,但是這樣就算帶回家也不開心。這天晚上宣告失敗,阿姨說可以幫我們抓,於是我們把外出籃留給阿姨,請阿姨抓到的時候通知我們。真的很感謝阿姨。那天跟學姐吃過晚餐以後就回新竹了,到家已經十一點半。
之後過了兩天都沒有消息,我想說週末再去台北一趟,兩天都在那邊跟阿喵培養感情,不知道能不能抓到呢?禮拜六下午就出發前往台北,到的時候大概是五點,沒有看到阿喵,但是阿姨在,阿姨說阿喵要晚一點才會來吃飯,叫我們晚一點再來,大概七八點的時候吧,於是我跟學姐就先去吃飯了。吃玩飯之後,就到學姐家等時間,等著等著接到阿姨打來的電話了,說貓已經抓到了,於是我跟學姐就趕快過去了。
到的時候,阿姨拿著籃子在等我們,說阿喵一直叫一直叫,她很心疼,說自己開了罐頭給她吃,吃一半就把她抓起來,好像在騙她,覺得很內疚。其實阿姨在講到阿喵的時候,都可以感覺到她很希望阿喵是真的喜歡我們才跟我們走,而不是被抓走的,我覺得有點難過,因為自己好似是因為單純喜歡阿喵漂亮就來找她,如果這樣把阿喵帶走,阿姨以後就看不到阿喵了,是有點難過,但是就用力說服自己說阿喵當流浪貓也很可憐,希望被我領養以後會比較幸福。
阿姨的女兒說阿姨很喜歡喵,叫我們之後要記得記照片給她,這樣阿姨才知道阿喵過得好不好。我們道謝以後就讓阿喵跟阿姨說再見,阿姨說不要再見啦,很難過,我跟學姐就帶著阿喵走了。
去了位在附近的中研動物醫院,醫生是個很high的人,講什麼都又快又激動。檢查的結果是:女生,兩歲多,跳蚤跟耳疥蟲都有點嚴重,沒有晶片,沒有結紮。醫生沒有說有沒有寄生蟲,只說目前要處理的嚴重問題就是耳疥蟲跟跳蚤。我們買了蚤不到跟疥蟲的藥,醫生示範了一下怎麼幫貓咪擦藥跟清耳朵。我看了一下記得動作。吃過飯以後我買了貓沙跟一些用具就回新竹了。本來是想說如果貓沒有跳蚤或是傳染病的話,我就把貓放到中壢給媽媽照顧,因為媽媽也喜歡貓,而且中壢還有兩隻之前撿到的米克斯(哭哭哩跟勇者大人),這樣阿喵也有伴,就不會白天我去上班的時候她就只能一個人無聊在家。但是既然有跳蚤,我就只好先帶回新竹。
回到新竹已經很晚了,因為知道貓剛換新環境都會先躲起來,其實可以不用去抓,只要讓她知道貓沙跟食物跟水在哪裡就好,她覺得環境安全的時候自然就會出來。我把房間整理了一下,貓沙準備好,食物跟水也準備好。把阿喵抱出來之後我把她放到貓沙上面,然後是食物跟水前面,讓她知道這些東西在哪裡,之後她就可以隨便躲沒關係。我先把床底下都塞住,因為床底下她進去了我很難抓到,也很難看到,沒辦法掌握阿喵的狀況,最後她選定的地方是我的書桌下面,那邊很ok,我可以很輕鬆的找到她。一般會躲個幾天,很正常,我想我不用太擔心。
一直到我睡覺,她都沒有出來。我關燈以後躺在床上,沒多久就聽到她窸窸窣窣的爬到便盆上面上廁所,果然是美少女阿,再怎麼難忍受也不會隨便上廁所,可以不吃飯,但是上廁所很重要。上完廁所以後好似咬了兩口飼料,然後開始探險房間,我想看仔細她在玩什麼,但是她聽到我翻身的聲音,又龜回書桌下面了。然後我也睡了。
禮拜天一整天阿喵幾乎都在書桌下面,我也不太去打擾她,偶而會去摸摸她,我發現她對摸摸的反應非常好,雖然是害怕的躲在書桌下,但是一旦摸摸,她就會很配合的脖子伸長,還會翻身橋姿勢讓你摸,更誇張的是,摸一摸我收手,她竟然還會爬出來一點點繼續討摸摸(但是太出來就不會了)。就這樣,偶而摸一下摸一下,到了晚上摸著摸著竟然她就整個爬出來了,然後我停手之後,她就跑去吃飯喝水上廁所,然後開始探險房間,看樣子是完全不怕了。沒想到一天就是環境了呢,很開心。雖然很開心,但是也有傷腦筋的地方,因為禮拜天晚上她就想要上我的床睡覺 orz 但是她還沒洗澡,而且跳蚤也很多,雖然她是美少女,我也只好先拒絕她了,擋了幾次之後她就乖乖的在地上睡著了。
禮拜一一起床,幫他清了耳朵滴了藥(禮拜天也做了兩次,一天要兩次,我都上下班的時候弄),下班以後估算就是蚤不到投藥之後的48小時,可以洗澡了!太高興了!但是在這之前要先梳毛跟剪毛!她身上有好多好髒的東西,一定要先處理掉,於是開始了可怕的修羅地獄,把她的毛全部梳開,一點一點的梳,深的梳不開的就用剪刀修掉,下班時去買貓用品的時候,美容院的阿姨有教我怎麼幫貓修毛,用排梳梳阿梳,梳不開的地方就用排梳把毛跟皮膚隔開,這樣剪毛就不會傷到貓,果然是大大受用。但是梳很久以後,發現重大災難,那就是阿喵的毛很多其實不是打結也不是口香糖,而是鬼針草,她的尾巴至少有上百根的鬼針草的種子,那東西對貓毛的黏著力超強的,很多貓毛就會因為一根鬼針草結在一起,鬼針草真的很多,以我的技術,如果要剪毛可能就得趨近剃光那樣醜,所以我開始手動把鬼針草一根一根挑出來。梳毛,剪毛,挑鬼針草,這個動作大概用掉我兩個小時吧,人都快哭出來了,終於挑完了。
帶著他進去浴室,開始洗!沒想到洗澡還蠻乖的,會逃是會逃,但是就是慢慢的蛇開這樣,輕輕一撈就回來了,不會抓不會咬,所以很順利的洗好了。之後抱出來擦乾,擦乾也是很順利,因為手隔著布擦她的時候,她會覺得是在摸摸,所以很配合,還會翻身呢。但是毛實在又多又長又密,擦了超久才稍微有點乾。下一個步驟就是所有幫貓洗澡的修羅階段了:吹乾。我還沒見過貓不怕吹風機的。有的是聽到聲音就開始爆衝,有的是被風吹到就會貓爪全開跟你格鬥,安分一點的就是逃命逃到不爽反過來抓你咬你這樣。
我把阿喵放在腿上,一手一邊摸她讓她乖乖,另一首把吹風機打開,我在想我比她還要抖。令人意外的是她聽到聲音沒事耶!一邊摸她,一邊嘗試把吹風機從很遠的地方開始慢慢吹……動了……她翻身了……她覺得這樣很舒服 -_-" 這是貓嗎 -_-?就這樣,很順的吹貓,但是因為毛真的很多很密,所以吹了一個多小時才全乾,我真的快累攤了。但是事情還沒完,因為洗澡的時候發現阿喵其實還有一些結塊的毛,所以我再次進行梳毛的動作,這次弄完之後阿喵真的超漂亮的了,又漂亮又香,有家貓的感覺囉。可惜我沒相機,這時候突然覺得之前沒買真是錯了,過兩天回中壢去拿弟弟的相機來拍 :P
既然弄乾淨了,那第一件事情就是把這個又香又漂亮的美少女放到床上,哈哈~放上去之後她也很開心,捲在我的棉被上面,然後就開始睡覺了(打這篇文章的時候她也睡在枕頭上喔)。弄到這個時候,已經十二點了,也就是說我辛苦了四個多小時,終於打理完畢,以後可以開始順利的過貓貓生活啦~
然後是附註:
我花 150 買的高級釣竿型逗貓棒,這傢伙鳥都不鳥。然後買了貓草想要給她吃,但是她逃得比什麼都快,看樣子她很不喜歡貓草,阿喵真的是貓嗎……
然後名字「阿喵」,其實我還沒想到什麼名字,但是照顧她的阿姨都叫她「阿喵」,所以我就跟著叫她阿喵了,叫她會有反應喔,很聰明…
最後是很難過的事情,在版上看到有人回的文章,「我突然想到,有次我去中研動物醫院,有個媽媽帶了虎班金吉拉去看醫生,因為貓咪亂吃東西然後拉肚子,媽媽一直說她很討厭貓,問我說我要不要,她說她很想把貓丟掉,那隻貓當初是有人帶去美容丟在中研獸醫院就再也沒來帶回,後來醫生給她養的,但是後來她又不喜歡,是三個兒子說養寵物就要好好照顧牠,不知道是不是那位媽媽丟出來的,那時那媽媽有點誇張,一直囔囔要送人,很討厭貓,想要丟掉.......」雖然不一定是同一隻啦,但是虎斑的長毛貓也不是滿街跑,地點跟品種都這麼巧合,我想阿喵大概就是當初那隻吧,被兩任主人拋棄的可憐孩子。話說她現在很幸福的躺在地上睡覺。
2009年5月13日 星期三
過
心中總會有幾句話在迴盪著「一流的程式設計師就是什麼都要會」、「事情總是簡單的」,雖然自己知道不一定是這樣,但是會希望每次我講這些玩笑話的時候,都會體醒自己能作到這樣的程度。還有一句常常想到的是「能把事情完成的,才是高手」這句不是玩笑話,所以也總是想著要把事情完成。
為了能成為「一流的程式設計師」,努力的督促自己在程式的每個領域都要接觸,就算不能精熟,也希望當自己遇到問題的時候,能夠知道要往哪邊找答案。為了什麼都要會,去學了多執行序,為了什麼都要會,去學了圖形化使用者介面,為了什麼都要會,去學了網路程式設計,為了什麼都要會,去學了記憶體管理,為了什麼都要會,去學了 3D 程式設計,為了什麼都要會,學了很多演算法,學了很多函式庫,學了很多語言,學了很多設計範式。
為了什麼都要會,學了 unit test,為了要用 unit test,就得會 dependence injection,為了作到 dependent injection,於是要使用 abstract factory,為了方便作到 abstract factory,所以要使用 Loki,為了要弄懂 Loki,所以要弄測底弄清楚 abstract factory design pattern,為了要弄清楚 abstract factory,所以要連帶把 factory 也弄懂。看著看著,又順便把 Design Pattern 複習了一下,然後也再努力看懂了一些 Modern C++ Design。
就是這樣,就一直學一直學,然後呢?然後呢?我就這樣一直學著程式設計,然後呢?雖然不是頂尖高手,但是也能把程式寫的還不錯,可是…我還能幹嘛呢?除了程式我沒別的東西了,把程式從我的成份裡面抽走以後,我什麼也沒有剩下了,我還剩下什麼阿?還剩下什麼阿?沒剩下什麼了,沒有了,可是我不想死耶,我好希望我會的東西不要浪費掉喔,真希望能把我會的東西都傳給別人喔,很希望自己能…可是我什麼都…我…我…我只是希望能作點什麼…什麼都好…希望能作點對這個世界大家都好的事情……
為了能成為「一流的程式設計師」,努力的督促自己在程式的每個領域都要接觸,就算不能精熟,也希望當自己遇到問題的時候,能夠知道要往哪邊找答案。為了什麼都要會,去學了多執行序,為了什麼都要會,去學了圖形化使用者介面,為了什麼都要會,去學了網路程式設計,為了什麼都要會,去學了記憶體管理,為了什麼都要會,去學了 3D 程式設計,為了什麼都要會,學了很多演算法,學了很多函式庫,學了很多語言,學了很多設計範式。
為了什麼都要會,學了 unit test,為了要用 unit test,就得會 dependence injection,為了作到 dependent injection,於是要使用 abstract factory,為了方便作到 abstract factory,所以要使用 Loki,為了要弄懂 Loki,所以要弄測底弄清楚 abstract factory design pattern,為了要弄清楚 abstract factory,所以要連帶把 factory 也弄懂。看著看著,又順便把 Design Pattern 複習了一下,然後也再努力看懂了一些 Modern C++ Design。
就是這樣,就一直學一直學,然後呢?然後呢?我就這樣一直學著程式設計,然後呢?雖然不是頂尖高手,但是也能把程式寫的還不錯,可是…我還能幹嘛呢?除了程式我沒別的東西了,把程式從我的成份裡面抽走以後,我什麼也沒有剩下了,我還剩下什麼阿?還剩下什麼阿?沒剩下什麼了,沒有了,可是我不想死耶,我好希望我會的東西不要浪費掉喔,真希望能把我會的東西都傳給別人喔,很希望自己能…可是我什麼都…我…我…我只是希望能作點什麼…什麼都好…希望能作點對這個世界大家都好的事情……
訂閱:
文章 (Atom)