2012年9月30日 星期日

程式上色

其實這是整理在BBS上的舊文,想說就把它轉到這裡來,是記錄如何在程式裡面加上顏色的控制,如果熟BBS的人應該很熟悉,似乎是一個公訂的標準上色方式,大家參考參考,也許可以讓你的程式增添不少色彩,能不能為人生上色就說不準了。

1. ANSI C:
開始上色格式:\033[x;y;zm
結束上色格式:\033[0m //其實就是設定為無屬性
\033就是escape字元,可以用\e來代替
x,y,z三數字分別定義字型格式、前景、背景。

x:
0 無屬性
1 明亮字體
2 暗淡字體
4 底線字體
7 反白字體
8 匿蹤字體 

y,z:
30~37 為字體顏色 黑紅綠黃藍紫青白
40~47 為背景顏色 黑紅綠黃藍紫青白

ex: printf("\033[1;33mtest\033[0m")
ex: printf("\e[1;33mtest\e[0m")

2. BASH shell:
第一種方法,可以用printf來實作,這和C就是一樣的了
至於echo,要用echo -e的方式,讓它解讀escape跳脫 ,之後的寫法也一模一樣。
值得一提的,這個設定對bash prompt也有用,比如說我的PS1參數的設定: [\e[1;32m\u\e[0m@\e[1;34m\h\e[0m \e[1;33m\W\e[0m]\$
很難看懂,總之,綠色的使用者名稱,藍色的電腦名稱跟黃色的路徑,還滿潮的XD

3. curses.h 雖然說現在用curses.h的程式好像滿少了,不過就寫一下吧,主要有五個步驟,那時候在ubuntu上測試是OK,還寫了一些東西,這次把它拿出來看發現已經看不懂了Orz。
    一、利用has_color() 偵測是否有顏色支援
    二、start_color() 開啟顏色支援
    三、init_pair(pair,f,b) 設定屬性
    四、attrset(COLOR_PAIR(pair)) 設定接下來使用這個屬性來輸出
    五、attroff(COLOR_PAIR(pair)) 關掉這個屬性
第三部的init_pair裡:
pair指的是要存在第幾個顏色設定暫存中 可以指定1~COLOR_PAIRS這麼多組,可以用printf("%d",COLOR_PAIRS)來得知,ubuntu10.10終端是64組。
f 指字體顏色,b 指背景顏色 可以用數字,也可以用在curses.h中定義好的代碼
COLOR_BLACK    0
COLOR_RED 1
COLOR_YELLOW 2
COLOR_GREEN 3
COLOR_BLUE 4
COLOR_MAGENTA 5
COLOR_CYAN 6
COLOR_WHITE 7
其實順序跟就跟之前提的一模一樣…
屬性的部分,也有預先定義好的可以使用,ex直接attrset(A_BOLD即可),不同的屬性可以用 | 來加總,不過也別加太多= =。
A_NORMAL 正常
A_STANDOUT 些許高亮度
A_UNDERLINE 底線
A_REVERSE 反白
A_BLINK 閃鑠
A_DIM 暗淡
A_BOLD 粗體
A_PROTECT 沒試過…不清楚
A_INVIS 匿蹤
A_ALTCHARSET 沒試過…不清楚
A_CHARTEXT 沒試過…不清楚

4. 參考資料:
基本上色:
https://wiki.archlinux.org/index.php/Color_Bash_Prompt
其餘關於ncurses:
http://doc.linuxpk.com/55549.html
http://fanqiang.chinaunix.net/a4/b2/20020626/060200258.html
http://tldp.org/HOWTO/NCURSES-Programming-HOWTO/

5. 結論: 顏色上色固然漂亮,可是也會讓輸出的內容變得較難閱讀,請不要太頻繁的使用。

2012年9月27日 星期四

C++ template使用注意事項

這篇是對上一篇python-like cpp譔寫過程中,遇到的一個疑問。
主因是我在寫一個含有template的Array class,雖然說內容是抄別人的,可是個人自作聰明做了些小修改,結果編譯怎麼樣就是不會過,後來才在google上找到解法,在這裡記錄一下。 我查到最完整的回答是:
http://stackoverflow.com/questions/8752837/undefined-reference-to-template-class-constructor
看到有人的評論是:This has been answered a million times.。我也知道這個問題對比我厲害一點的人都是常識了,可能N年前修資料結構甚至程式設計的時候就被表過了,不過我倒是第一次遇到,大概是從來沒好好的寫過程式吧。
以下就整理一下問題的相關背景、發生原因和解決方式,主要都參考上面的回答。

1. 問題背景:
程式架構其實相當的芭樂,自定義的class Array:Array.h, Array.cpp,記錄一個template類型的動態陣列,會在初始化時排定空間,透過get和set 對其中的元素取值或賦值。
當我在main裡面使用Array a(10),然後將main.cpp, Array.cpp一同編譯時,就出現了下列的錯誤訊息:
main.cpp:(.text+0x1d): undefined reference to `Array::Array(int)'
main.cpp:(.text+0x3d): undefined reference to `Array::set(int const&, int&)'
main.cpp:(.text+0x6f): undefined reference to `Array::get(int const&)'
main.cpp:(.text+0xb8): undefined reference to `Array::get(int const&)'
main.cpp:(.text+0xd8): undefined reference to `Array::~Array()'
main.cpp:(.text+0x183): undefined reference to `Array::~Array()'
總之一切定義、用到的function都找不到。

2. 問題原因:
我們可以把class分為三個階段:Array.h檔內宣告、Array.cpp檔內定義、main.cpp內使用。 這個問題的原因在於,gcc在編譯時並不會同時編譯所有檔案,而是將檔案視為獨立,最後透過linker將上述的檔案連結在一些,消除其中的reference。
template只是一個標註,告訴compiler這個型別還未定型並可置換,當指定如何使用時,才會解開置換並編譯相關的原始碼,compiler在處理Array.cpp時,完全不會編譯Array<int>, Array<float>…等class,除非如下文所示,利用強制的方式指定編譯某個型態;處理main.cpp時,gcc的確在Array.h裡面看到Array的宣告式,因此它將Array的function設為reference,讓linker去尋找相對應的執行碼。
最後linker要連結時就發生問題了,main.cpp要求Array的code,但在Array.cpp裡面卻沒有,就成了undefined reference。

3. 解決方法:
這個問題有三個可行的解決方法:
第一是不要像我一樣傻傻的把class分成兩個檔案,而是直接將宣告和定義寫在相同的.h裡,如此一來,編譯main.cpp時遇到Array即可在Array.h裡面找到對應的template code並編譯。
第二種解決方法,是在Array.cpp的檔案末端,明確的告訴編譯器,這裡有哪些型別的class需要編譯:
template class Array;
template class Array;
… 
產生的Array.o內部,就有Array編譯完成的code供連結。
第三種方法是直接在使用的地方#include Array.cpp

三種方法各有優劣。
第一種方法容易加大原始碼的大小,並把定義和宣告寫在同一個檔案內,也會增加編譯時的成本。
第二種方法的彈性較低,寫了哪些就只能用哪些,對於大多數的STL物件或許還OK,如果程式中有自定義的class就成了大問題;現今STL為了支援泛型,都是用第一種方法來譔寫。
第三種方法當然也OK,但其實跟第一種方法沒什麼兩樣,甚至還會增加引入時的複雜度和錯誤的可能性。

4. 結論:
template算是泛型程式的基礎,讓程式碼可以依據使用者的需求,編譯出不同的執行檔。 也因此,在使用template時,程式的宣告和定義無法分離於不同的檔案。
一般來說,採用將宣告與定義放在同一個檔案內是基本的做法,可以保有未來的擴增性;如果這份程式只是自己的project內要使用,確定只支援哪些類型,就可以分離兩部分,並明確指明編譯哪些類型。

5. 致謝: 本文內容感謝黃偉寧(AZ Huang)同學指點,感謝諸位的觀看

2012年9月26日 星期三

C++ python-like debug message


在C++下,如果程式寫得大一點,又有自己寫的資料結構、繼承、多型之類的,如果有地方寫錯了,這時debug起來通常相當麻煩;過去筆者都是用gdb跑到當掉的地方,再用backtrace幾次,看看是哪裡出了問題,可是這樣還是有點麻煩;又次用到srilm等套件,backtrace 10次還是不知道錯在哪...
最近跟柏翰學長學到一個debug的技巧(雖然說學長表示他也是跟別人學的),可以在C++上實現python-like的錯誤訊息,很清楚的就能知道錯誤是在哪發生的,在這裡把這個方法整理一下,分享給大家。

1. python-like debug message:
python的debug訊息好處在於,在一個function中出錯,它會有自動traceback的功能,一路把呼叫該錯誤部位的function一路吐出來:
例如一系列函數呼叫如下:err1()->err2()->err3(),然後在err3裡raise一個error,console會輸出
Traceback (most recent call last):
  File "./error.py", line 17, in <module>
    err1()
  File "./error.py", line 14, in err1
    err2()
  File "./error.py", line 11, in err2
    raise(KeyError)
KeyError
其印出的格式整理如下,含$為變數:
File $filename, line $lineno, in $functionname
error code

2. C++ exception:
C++ 提供了try, throw, catch來進行例外的處理機制,相關文件請眾位參考網路上的其他資源,例如:
http://caterpillar.onlyfun.net/Gossip/CppGossip/ClassTemplate.html
這裡只簡單帶過,簡而言之,在try裡造成發生錯誤的情況,throw出一個錯誤,由catch端決定如何處理。
我們利用這樣的處理機制,遇到出現錯誤的狀況,例如提取不存在的資料時,直接throw出一個錯誤,並利用全域的catch(...)將它接下來,利用compiler的內定巨集__FILE__, __LINE__可以編寫出適當的錯誤訊息;同時,可以在catch中再throw一次,將錯誤再往上一層函數丟,如此一來就能達到function traceback的功能,整體程式碼的架構如下:
function err1(parameter)  //throw error function
{
#ifndef NDEBUG
 try{
#endif
 /* code */
#ifndef NDEBUG
 } catch(...) {
 cout << "Traceback:\n";
 cout << "File: " << __FILE__ << ", line "<< __LINE__ << ", in " << __func__ << endl;
 throw; 
 }
#endif
}
利用#ifndef NDEBUG,可以快速重新編譯沒有這些debug訊息的發行版。
利用巨集,則可以讓程式碼看起來更簡潔一點
#define STARTEXCEPTION try{
#define ENDEXCEPTION \
 } catch(...) {\
 cout << "Traceback:\n";\
 cout << "File: " << __FILE__ << ", line "<< __LINE__ << ", in " << __func__ << endl;\
 throw; \
 }

function err1(parameter)  //throw error function
{
#ifndef NDEBUG
STARTEXCEPTION
#endif
 /* code */
#ifndef NDEBUG
ENDEXCEPTION
#endif
}

3. 範例:
我試著提出一個小小的範例,我們現在寫一個Array的object,可以在宣告時指定大小,get 和set 其中的內容(好吧我承認這個code其實就是cv上面連結來的,不過在template的class時我遇到一個template的問題,透過google大神解決了,之後再來寫一篇。)
在get和set的地方,原本都會去檢查index有沒有在範圍之中,否則會存取不存在的值,那我們現在在code中加上上述的exception,下文中,粗體為加入的部分。
template<class CType>
CType Array<CType>::get(const int &i)
{
#ifndef NDEBUG
STARTEXCEPTION
#endif
 if (isSafe(i)) {
  return _Array[i];
 } else {
  cout << "Index out of range\n";
  throw 1;
 }
#ifndef NDEBUG
ENDEXCEPTION
#endif
}
在main裡面,當然也要對呼叫function的地方加上STARTEXCEPTION跟ENDEXCEPTION,當我試圖get超範圍的值時,就會出現錯誤。
Index out of range
Traceback:
File: array.h, line 59, in get
Traceback:
File: main.cpp, line 31, in main
terminate()
我認為,這個方法雖然提供了明確問題的發生途徑,但仍有下列三個問題:
正常的exception做法,應該會去自定義exception handling的class對exception進行處理,才能真正的避免問題,如記憶體漏失;這個方法只是要讓程式終結,輸出較明確的錯誤訊息,而非解決該excetion的發生。
另外,function中使用的__LINE__的行數是大略值,__func__的巨集好像也還沒統一,隨不同家的編譯器也會有不同的定義:
http://stackoverflow.com/questions/2192680/macro-keyword-which-can-be-used-to-print-out-method-name
最後,這個方法深入所有的function,這在程式用到大量函式庫的狀況下,把每個function加上這個code的代價相當昂貴。

4. 如何佈署?
還有另一個問題是,要如何將這些內容佈署到code當中?
在這裡有兩個不同的方式:
第一種是學長所用,取代"^{","^}"為STARTEXCEPTION和ENDEXCEPTION,這個方法極度要求格式的一致,function的括號一定要在行首,像我code格式都亂七八糟的人就不適合。
第二種是用vim的snipmate,定義STARTEXCEPTION和ENDEXCEPTION的pattern ,在指定的地方插入,搜尋時可以利用vim plugin: taglist,可以幫你列出所有的function,並支援快速跳到function 的所在地。

其實我認為應該還有更好的方法,或許是利用cscope或ctags來parse出所有function 的所在地,自動或詢問要不要加上debug訊息,不過目前還沒找到這樣的方法。

5. 結論:
本方法利用C++的exception, throw的功能和g++的MACRO,實現python-like的錯誤訊息,可以有效加速C++複雜程式上的debug過程;利用vim的自定義巨集,我們可以自動在所有函數加入此功能,並用ifndef NDEBUG guard的方式,確保未來可輕易在發行版中移除此功能。
但另一方面,此方法其實無法解決問題,僅是幫助指出問題所在對終止程式,真正防止程式出錯或記憶體管理還是要靠完整的exception handling。

6. 致謝:
此文章感謝柏翰學長的指導
並感謝眾位的觀看,不好意思浪費了大家的時間。

2012年9月22日 星期六

我的vim設定

最近梗比較少,寫不出什麼有用的東西
整理了一下自己的vim設定,就把自己的設定跟plugin分享一下好了

1. plugin
裝的plugin主要有幾個:
autoComlPop: 輔助omnicomplete的文件單字補齊
http://www.vim.org/scripts/script.php?script_id=1879
LargeFile: 開大檔用,其實比較少用到
http://www.vim.org/scripts/script.php?script_id=1506
surround: 快速修改相對應的標籤
http://www.vim.org/scripts/script.php?script_id=1697
NERDtree: 快速瀏覽目錄
http://www.vim.org/scripts/script.php?script_id=1658
snipMate: 自訂關鍵字補齊功能,缺點是會變笨
http://www.vim.org/scripts/script.php?script_id=2540
taglist: 程式碼概略瀏覽
http://www.vim.org/scripts/script.php?script_id=273

附帶一提,這些plugins全部都裝在~/.vim裡面
我發現可以用dropbox,建一個setting的資料夾
把.vim sync到dropbox上,這樣以後重裝電腦
只要把這個資料夾再複製回來,這些plugin就都裝好了

2. vimrc
我的vimrc如下:
paste.plurk.com/show/1313103
一開始是編碼和layout的設定,然後是各個plugin的設定
用F12可以開taglist
用F7可以用當前的makefile進行make
之後可以用F8列出quickfix所有的錯誤編譯訊息
Ctrl+n Ctrl+p在錯誤內容進行跳躍

omnicomplete的部分是針對各種檔案格式,去設定omnifunc要用哪一種
比較奇怪的是,在vim73上面,omnicomplete的功能被嚴重限縮了
目前還不知道原因Orz

最後就是一些filetype的link
比如說把php, html的filegype交互設定,這樣在php檔裡面也可以使用html 的snipMate快速補齊;cuda 和lex也是相同的道理
3. 結論
這是一篇廢文
其實vim功能相當的強大,能安裝的plugin也多的很
這個設定只是分享,使用習慣還是大家平時用習慣最重要