2017年9月22日 星期五

Windows的傾印檔 (Dump File)

甚麽來的?

這是一個程式在某個瞬間的快照(Snapshot),可把當時程式的運行狀態以及使用的記憶體像照片一樣拍下來,之後便可在開發軟件內嘗試重現當時的情況。在程式不知道為何出錯時,如果能取得它當時的Dump file,對於debug找出問題所在是很有用的。

下面介紹在Windows取得傾印檔的方法。

如何取得

當程式在運行途中出現錯誤而被終止運行(Crash/Terminated)的時候,作業系統有時會幫它儲存一個傾印檔(Dump File)。你也可以選擇自己開啓Task manager,在該程式名稱上按右鍵選擇Create Dump File,又或者使用微軟提供的procdump.exe來監視一個程式:

procdump -t PID

記得填入該程式的Process ID,可在程式開啟後Task Manager查到。
procdump還有許多功能,可看:https://docs.microsoft.com/en-us/sysinternals/downloads/procdump

取得之後如何使用


在取得Dump File後,可以開啓Visual Studio或者WinDbg之類的軟件來打開。
只要載入正確的Symbols檔(一般是pdb,可以幫你指出exe中的某行是source code的哪一行),再加上source code,便可得悉程式是在運行到哪一行時出錯,方便debug。

注意:

  1. 如果程式是使用Release模式編譯的,因為經過最佳化(Optimization),不一定能夠和source code一行一行對應,因此頂多只能知道大概的位置,而不能準確指出是哪一行出問題。
  2. 有時候程式或者函式庫會自帶Exception handler,即是某行出錯時會被handler抓到,然後在handler function內執行後再把Exception交給上面,最後才終止運行。這種時候我們需要查找Exception object或pointer中的Stack Trace,它上面會寫原本出問題的是哪一行。可參考:https://support.microsoft.com/en-us/help/313109/how-to-find-the-problem-exception-stack-when-you-receive-an-unhandlede
  3. 記得在編譯程式的時候保留當時的source code以及pdb!pdb是用來儲存debug information,包括exe中的machine code對source code的mapping。一定要保留在編譯exe時一起編譯的pdb,因為只要source code稍有改動後編譯出來的exe已經是完全不同的東西,不配對的pdb是無法debug的。

2017年7月24日 星期一

類別成員函數 (Class member variables) 常見的分類

在編寫Class的時候需要define很多member variables(又叫fields),供Class的member functions(又叫method)使用。但經常會看到一些比較大的Class被人加了許多member variables(從數十個到上百個都有),而且都是亂排,只看到一堆排列得沒有意義的variable會讓人很難了解這個class想做甚麼。寫Class寫多了見多了的時候會覺得其實member variable來來去去也是那幾個作用,所以我自己寫Class時通常會把member variable分成下面四類,同類的會放在一起,方便以後查看,也能提醒自己別漏了initialize某些variable:

Data Containers

裝著一些程式所需要用到的資料,尤其是一些複雜的資料結構(Data Structure),如map、list、queue。

Resources

一般是一些來自Library Class 的 Instance,當中常需要用到一些作業系統的API,或者IO用。
例子:

  • Socket (供UDP/TCP連接用)
  • Event
  • Critical section
  • File

States

用來記錄目前程式的狀態。這些variable一般都需要在Constructor initialize,避免之後第一次存取的時候出錯。
例子:

  • 上一次執行某程序的時間
  • 現在程式運行到哪一個階段 (Stage)
  • 目前是否連接著伺服器

Config

程式一開始的時候會載入的設定,一般來説之後較少或不會更改

2017年6月1日 星期四

如何寫出易讀的程式碼?

寫程式除了自己寫出新的程式碼外,也常常需要修改其他人寫的程式碼,但見過許多許多不同的程式碼後,體會到不同人寫出的程式碼的風格差異大,質素參差,不整齊,又沒有comment。這樣的程式碼卻往往留存下來的原因就是因為大家都看不懂它到底想做甚麼,所以不敢碰它,怕一旦改了就會導致程式錯誤。。尤其是程式的作者往往已經不在了,又或者連他自己也不記得自己當時在寫甚麼。

個人認為Let code speak for themselves這一點是非常重要的,即是讓其他人單看程式碼就能看得懂,不用問作者,不用依賴其他文件。而且寫得好且漂亮的Code也能讓人讀起來沒那麼辛苦(起碼會讓人想讀),提高生產力。

以下是我自己會使用的一些原則,盡量讓自己的程式碼能讓其他人看得懂:

1. 直接了當,沒有comment也能看得懂

最好的code是用variable和function名稱本身解釋一切,你應該做的最多就是解釋「為何要這樣做」,而不是「在做甚麼」。當然把程式碼分成一段段再用comment來大致解釋那一段的用途會更好。

2. 一個屏幕顯示整個function

控制function的長度,最好在一個屏幕便能顯示function的所有(或大部分)內容
如果開始感覺function的長度超出你可以一眼理解和控制的範圍,請把部分內容拆成另外一個function,並用容易理解的命名,這樣便能讓人容易掌握這個function的input和output。

3. 用scope來限制variable的影響力

很多variable的用途是非常短暫的,可以利用scope來清楚標明variable的影響力,這樣別人便能容易理解這個variable只會在某小段區域存在,不會被後面用到。 例如下面的code,nLineLen只是用來設定szLine的,所以用完便應捨棄:
char* szLine = NULL;
{ 
    const int nLineLen = 100;
    szLine = new char[nLineLen + 1];
    memset(szLine, 0, nLineLen + 1);
}
不過要留意的是,有些語言以及比較舊的compiler會無視scope。

4. 別亂comment code

經常看見有人會把一大段code都comment掉,也許這段code只是用來debug,又或者是因為程式要求有改過所以已經不適用但又不捨得刪掉,結果讓這種commented code越來越多,影響閱讀。

如果這段code是不需要的,請無情刪了它,又或者是把舊版本放進Version Control之後再在新版本刪了它。如果因為某些原因真的不捨得移除,就起碼加上註解,解釋這段code為甚麼會被comment。

5. 幽默

如果這段程式碼不需要提交給客戶的話,便可大膽一點,comment一些有趣或者搞笑的事情,或者是用較口語化的語氣來說明一些程式碼的功用,起碼讓人看起來能夠會心一笑,不會覺得沉悶。

2017年3月27日 星期一

using namespace std

一開始學C++便看到每個程式的最頂,#include的下方都會有一句:
using namespace std;

所有C++ Standard Library中的class(例如string、map、ifstream)都屬於std這個namespace下。當你要在自己的程式內使用它們時,便需要在前方加上std::,例如std::string。但如果使用了上述那句的話,便不需要加上std::,而是讓compiler自動幫你加。

只不過這樣做的缺點就是當你自己有class和C++ Standard Library中的任何一個class撞名,compiler便可能會選錯namespace,又或者直接顯示ambigious的錯誤。解決這問題的方法主要有兩個:

  1. 盡量在.cpp中加入這一句,而非.h,因為header file很容易被其他文件include,讓缺點擴散(?)
  2. 指定某些class使用std namespace,而非一刀切全部用std。代碼如下:
using std::string;
using std::cout;
using std::ifstream;

2017年3月6日 星期一

Struct Member Alignment

無論用TCP還是UDP收資料都需要解碼(又叫decode或者拆packet),即是把收回來的byte array根據protocol來處理,從而抽取一些有用的資料。而我見過有些code是直接用type cast把byte array轉換成struct,然後很神奇地就會順著struct的member的順序來填入這些member variable了。

一開始我上網搜尋看到有人指出這個行為並非C++標準,而且OS可能會自行把struct的member拆開令它們在記憶體中不是連續的(例如Windows會以4個Bytes為一個DWord以提升效率),這樣的話type cast就會搞到解碼錯誤,但只要加上一些OS提供的特殊語法就沒有問題。

例如Windows的話,要在struct前後加上#pragma pack,並且在前面說明以1 Byte為pack的單位。
#pragma pack(1)
struct ABC{
/* member variables */
}
#pragma pack()

當然,我覺得始終最好的方法是在constructor寫明怎樣解碼,這樣除了比較清晰容易檢查之外,也是一個跨平台的解決方法。

參考資料:
http://blog.xuite.net/kamory0931/fightdreamer/52223417-%E7%B5%90%E6%A7%8B%E7%9A%84%E5%B0%8D%E9%BD%8A(Struct+Member+Alignment)