C++代碼和調試

來源:互聯網
上載者:User

    公司開發項目調試的時候遇到一個頭疼的問題,一個函數給很多地方調用了,但我只要某個參數值時執行該斷點,這種進階的調試從來沒接觸過,上網搜了一下,果然有這方面的調試技術,下面是我找的資料,挺有用的,分享一下。

    原文連結是:http://hhfighting.blog.163.com/blog/static/55700323200922093543827/

 

C++代碼和調試

本部分教程主要介紹了良好的C++代碼風格、如何書寫安全的代碼以及在Visual C++環境下的程式調試技術,這些內容對於新員工從學產生長為真正的程式員,逐步參與實際項目的開發工作,以及閱讀第三方代碼非常重要。

1 規範易懂的代碼

現階段軟體開發,都要依靠團隊的合作。程式員不再是個人英雄主義的代名詞,程式員一方面要依賴大量其他程式員完成的代碼,一方面又提供大量代碼給其
他人使用,代碼實際上具備了兩個要素:首先是可靠的提供某種功能,其次是清楚地表達作者的思想。任何交流都必須有一定的規範才能進行,體現在代碼中就是規
範易懂。另外,規範易懂的代碼才是可重複使用的,規範的代碼具有更長的壽命,具有更好的可維護性,也更方便後期的擴充。

1.1 好代碼的幾個特徵

怎麼樣的代碼才算規範易懂,體現在細節上會有無數的爭論,實際上無論風格和習慣如何,好的代碼具有幾個共同的特徵:
1. 良好的命名:好的變數名和函數名,讓閱讀代碼的人馬上就知道該變數或者函數的作用,很容易就能理解程式的大概結構和功能。程式員有必要理解匈牙利命名法。
2. 一致性:一致性帶來更好的程式,一致的代碼縮排風格能夠顯示出代碼的結構,採用何種縮排風格並不重要,實際上,特定的代碼風格遠沒有一致的使用它們重要。
3. 注釋:注釋是協助程式讀者的一種手段,程式作者也是未來的程式讀者之一。最好的注釋是簡潔地點明程式的突出特徵,或是提供一種概觀,協助別人理解程式;但如果注釋只是說明代碼已經講明的事情,或者與代碼矛盾,或者以精心編排的形式迷惑幹擾讀者,那就是幫了倒忙。

1.2 養成好習慣

前面已經提過,特定的代碼風格遠沒有一致的使用他們重要,所以,把過多的精力放到A or B的選擇上是浪費時間,你要做的是堅持。如何書寫規範易懂的代碼,如何養成良好的習慣,下面是一些提示。

1. 按照匈牙利命名法給變數和函數命名。
2. 遵循國際流行的代碼風格。
3. 寫代碼的同時就遵循你的命名規範和書寫風格,千萬不能事後補救。
4. 利用工具(Parasoft C++ Test)檢查你的代碼,評估一下自己形成良好的習慣沒有。
5. 堅持不懈直到養成習慣。

2 編寫安全可靠的代碼

在大型應用軟體系統中,各個程式碼片段共同構成完整的系統,代碼間的互動非常頻繁,程式崩潰往往並不在錯誤發生的時候就發生,而是延遲了一段時間,經
過數個函數之間的中轉後才發生,此時定位和尋找錯誤非常費時費力,如何才能及時反映程式中的錯誤,如何在代碼中避免一些幼稚的語義錯誤呢?一個函數往往會
被其他程式員拿來使用,但是他怎麼能夠正確的使用其他人編寫的函數呢?這部分內容能夠(部分)協助解決這些問題。

2.1 契約編程
契約編程(Design by Contract)的思想在C++聖經級的著作,C++之父Bjarne
Stroustrup的《C++程式設計語言》中略微提到過,OO領域的聖經級著作《物件導向軟體構造》以大篇幅闡釋了契約編程,現在越來越多的軟體開發
人員認識到契約編程的重要性,並逐步地在實際工作中採用契約編程。
對契約編程簡單的解釋是:對實現的代碼塊(函數、類)通過規定調用條件(約束)和輸出結果,在功能的實現者和調用者之間定義契約。
具體到我們的工作,開發人員應該對完成的每個函數和類,定義契約。契約編程看似平淡無奇,對程式開發沒有什麼具體的協助,實際上,契約編程在開發階段就能夠最大程度的保證軟體的可靠性和安全性。

實際工作中,每當你需要使用其他程式員提供的模組,你並不知道如何調用,也不知道你傳入的參數是否合法,有時候對於功能模組的處理結果也不敢相信。這些本
來應該很明顯的資訊因為模組提供者沒有顯式的提供,造成了調用者只能忐忑不安的摸著石頭過河,浪費了大量時間,而且為了讓自己的代碼更安全可靠,在代碼中
做了大量的判斷和假設,造成代碼結構的破壞和執行效率的損失,最後,調用者依舊不能確保自己的調用是正確的。
而契約編程通過嚴格規定函數(或類)的行為,在功能提供者和調用者之間明確了相互的權利和義務,避免了上述情況的發生,保證了代碼品質和軟體品質。

2.2 主動調試
主動調試指在寫代碼的時候,通過加入適量的調試代碼,協助我們在軟體錯誤發生的時候迅速彈出訊息框,告知開發人員錯誤發生地點,並中止程式。這些調試代碼只在Debug版中有效,當經過充分測試,發布Release版程式的時候,這些調試代碼自動失效。
主動調試和契約編程相輔相成,共同保證軟體開發的品質。契約編程相當於經濟生活中籤訂的各種合約,而主動調試相當於某方不遵守合約時採取的法律懲罰措施。
各種開發語言和開發工具都提供這些調試語句,標準C++提供了assert函數,MFC提供了ASSERT調試宏協助我們進行主動調試,在實際工作中,建議統一使用MFC的ASSERT調試宏。

2.2.1 參數檢查
對於編寫的函數,除了明確的指定契約外,在函數開始處應該對傳入的參數進行檢查,確保非法參數傳入時立即報告錯誤資訊。例如:
BOOL GetPathItem ( int i , LPTSTR szItem , int iLen )
{
ASSERT ( i > 0 ) ;
ASSERT ( NULL != szItem ) ;
ASSERT ( ( iLen > 0 ) && ( iLen < MAX_PATH ) ) ;
ASSERT ( FALSE == IsBadWriteStringPtr ( szItem , iLen ) ) ;
}
對指標的檢查尤其要注意,通常程式員會這樣進行檢查:
// An example of checking only a part of the error condition
BOOL EnumerateListItems ( PFNELCALLBACK pfnCallback )
{
ASSERT ( NULL != pfnCallback ) ;

}
這樣的檢查只能夠排除指標為空白的情況,但是如果指標指向的是非法地址,或者指標指向的對象並不是我們需要的類型,上面的例子就沒有辦法檢查出來,而是統統認為是正確的。完整的檢查應該如下:
// An example of completely checking the error condition
BOOL EnumerateListItems ( PFNELCALLBACK pfnCallback )
{
ASSERT ( FALSE == IsBadCodePtr ( pfnCallback ) ) ;
}

2.2.2 內部檢查
恰當地在代碼中使用ASSERT,對bug檢測和提高調試效率有極大的協助,下面舉個簡單的例子加以說明。
switch( nType )
{
case GK_ENTITY_POINT:
// do something
break;
case GK_ENTITY_PLINE:
// do something
break;
default:
ASSERT( 0 );
}

在上面的例子中,switch語句僅僅處理了GK_ENTITY_POINT和GK_ENTITY_PLINE兩種情況,應該是系統中當時只需要處
理這兩種情況,但是如果後期系統需要處理更多的情況,而此時上面這部分代碼又沒有及時更新,或者是因為開發人員一時疏忽遺漏了。一個可能導致系統錯誤或者
崩潰的bug就出現了,而使用ASSERT可以及時地提醒開發人員他的疏忽,儘可能快的消滅這個bug。

還有一些情況,在開發人員編寫代碼時,如果能夠確信在某一點出現情況A就是錯誤的,那麼就可以在該處加上ASSERT,排除情況A。

綜上所述,恰當、靈活的使用ASSERT進行主動調試,能夠極大提高程式的穩定性和安全性,減少調試時間,提高工作效率。

2.3 有用的代碼風格
一些好的代碼風格也能夠協助你避免一些幼稚的、低級的錯誤,而這種錯誤又是很難檢測到的。由於C++語言簡潔靈活的
特性,有時候敲錯一個字元,或者漏敲一個字元,都有可能造成極大的災難,而這種錯誤並不是隨著你的編程水平和經驗的提高就能逐步避免的,誰都會敲錯字元,
對吧。
比如程式員經常將等於邏輯判斷符==誤敲成賦值運算子=,對於我來說就不太可能程式運行出錯後才發現,因為我的習慣是,對於邏輯判斷,將常量置於==的左邊,如果我誤輸入了=,那麼編譯的時候編譯器就會報錯。
if( INT_MAX == i )

3 Visual C++調試技術
檢查代碼直到頭暈眼花也沒有發現錯誤,一運行程式就死機,只好祭出最後的法寶:調試器。Visual
C++調試器可以稱得上Windows平台下最好的C/C++調試器了,而且Visual
C++調試器還可以調試用其他語言如Delphi、Java編寫的程式,可謂功能強大。
儘管Visual C++調試器具有如此大的威力,它也只能協助你發現一些隱藏的邏輯錯誤,對於程式設計和結構的缺陷無能為力。
程式員最常用到的Visual C++調試技術有設定斷點、跟蹤呼叫堆疊和反組譯碼調試,其他編譯器功能均為調試中的協助工具輔助,因為反組譯碼調試需要程式員具備組合語言知識和語言底層結構,這裡不再介紹。

3.1 調試的先決條件
專業調試者有一個共同的特點,即他們同時也是優秀的開發人員。顯然,如果你不是一個優秀的開發人員,那麼你也不可能成為調試專家,反之亦然。以下是要成為一名高水平的,至少是合格的調試者或者開發人員所需要精通的領域。

1. 瞭解項目:對項目的瞭解是防範使用者介面、邏輯及效能方面的錯誤的第一要素。瞭解各種功能如何在各種源檔案裡實現,以及在哪兒實現,你就能夠縮小尋找範圍,很快找出問題所在。
2. 掌握語言:掌握項目所使用的語言,調試者(開發人員)既要知道如何使用這些語言進行編程,還要知道這些語言在後台作些什麼。
3. 掌握技術:要解決棘手的問題,第一個重要步驟就是抓住所用技術的要領,這並不意味著你必須對所用技術的一切細節都一清二楚,而是說你應該對所使用的技術有一個大概的瞭解,而且更重要的是,當需要更詳細的資訊時,你應該確切的知道在哪兒尋找。
4. 作業系統和CPU:任何項目都實際運行在特定的作業系統和特定的CPU,對作業系統瞭解越多,對尋找錯誤協助越大;從理論上來說,掌握組合語言,你就可以調試解決任何bug。

無論從事什麼工作,只要是經常從事技術工作的人,都必須不斷地學習以跟上技術的發展,更不用說想幹得更好或是想走在技術發展的前沿。經常閱讀優秀的
技術書籍和雜誌,多動手編寫一些公用程式,閱讀其他優秀開發人員的代碼,作一些反組譯碼工作,都會有效協助你提高開發和調試水平(尤其當你將這四者有機結合起
來)。

3.2 調試過程

確定一個適用於解決所有錯誤的調試過程有一定的難度,但John Robbins提出的調試過程應該說是最實用的:
1. 複製錯誤
2. 描述錯誤
3. 始終假定錯誤是自己的問題
4. 分解並解決錯誤
5. 進行有創見的思考
6. 使用調試協助工具輔助
7. 開始調試工作
8. 校正錯誤已被更正
9. 學習和交流

對錯誤進行描述有助於改正錯誤,同時也能夠得到同事們的協助。逐步縮小問題範圍、排除不存在錯誤的程式碼片段,直到找到問題所在,是解決所有問題的普遍
適用方法。有些奇怪的錯誤需要你把視線從代碼堆轉移到諸如作業系統、硬體環境等其他方面去。善用各種調試協助工具輔助能夠節省你大量的時間,而且某些工具本身
就不會給你犯有些錯誤的機會。當你解決了一個bug,停下來思考一下,什麼導致你(或他)犯了這樣的錯誤,以後如何避免?

要記住調試器僅僅是個工具,就好比一隻螺絲起子,你讓它做什麼它就只做什麼,真正的調試器是你自己腦子中的調試思想。

3.3 斷點及其用法

在Microsoft Visual
C++調試器中在原始碼行中設定一個斷點很簡單。只需要開啟源檔案,將游標放在想要設定斷點的程式碼上,按下F9快速鍵就可以了,再次按下F9快速鍵就會
取消斷點。當運行該程式碼的代碼時,調試器將在所設定的位置處停止。這種簡單的位置斷點的功能極其強大,經過統計,只需要單獨的使用這種斷點,就可以解決
99.46%的調試問題。

如果程式並不是每次運行到斷點處都會發生錯誤,那麼不停地在調試器和應用程式之間穿梭很快就會讓人厭倦,這時進階斷點就派上了用場。從本質上來講,
進階斷點允許你將某些智慧寫入到斷點中,讓調試器在執行到斷點處時,只當程式內部狀態符合你指定的條件時才在斷點處中斷程式運行,並切換到調試器中。

按下Alt+F9快速鍵彈出Breakpoints對話方塊,瀏覽一下對話方塊發現該對話方塊分為Location、Data和Messages三頁,分別對應三種斷點:
1. 位置斷點:我們通常使用的簡單斷點均為位置斷點,我們還可以設定斷點在某個二進位地址或任何函數上,並通過指定各種限定條件來增強位置斷點的功能。
2.
運算式和變數斷點:調試器會讓程式一直運行,直到滿足所設的條件或者指定資料更改為止。在Intel
CPU上,這兩種斷點都儘可能通過CPU的特定調試寄存器使用一個硬體斷點,如果能夠使用調試寄存器,那麼程式將能夠全速運行,否則調試器將逐步執行每個
彙編指令,並每步都檢查條件,程式的運行速度將極其緩慢甚至無法運行。
3. Windows訊息斷點:使用訊息斷點,可以讓調試器在視窗過程接收到一個特定的Windows訊息時中斷。訊息斷點適用於C SDK類型的程式,對於使用MFC等C++類庫的程式(應該是絕大多數)來說,訊息斷點並不實用,可以變通地使用位置斷點來達到同樣效果。
各種進階斷點的設定在MSDN中有詳細的介紹,請在Visual C++子集下搜尋主題Using Breakpoints: Additional Information並閱讀相關內容。

3.4 呼叫堆疊

有時候我們並不清楚應該在哪裡設定斷點,只知道程式正在運行就突然崩潰了,這時候如何定位到出錯地點呢?這時的選擇就是查看呼叫堆疊,呼叫堆疊可以協助我們確定某一特定時刻,程式中各個函數之間的相互調用關係。

法是當程式執行到某斷點處或者程式崩潰,控制權轉到調試器後,按下Alt+7快速鍵,彈出Call
Stack視窗,你可以看到當前函數調用情況,當前函數在最上面,下面的函數依次調用其上面的函數。在Call
Stack視窗的快顯功能表上選擇Parameter Values和Parameter Types可以顯示各個函數的參數類型和傳入值。

3.5 使用跟蹤工具

有些時候,我們希望瞭解程式中不同函數之間的協作關係,或者由於文檔的缺失,希望能夠確認函數在不同情況下被調用時的傳入參數值。這時使用斷點功能就過分麻煩,而呼叫堆疊只能查看當前函數的被調用情況,一種較好的方法就是使用TRACE宏以及相對應的工具。

序(Debug版)運行中,一旦運行到TRACE宏,就會向當前Windows系統的調試器輸出TRACE宏內指定的字串並顯示出來,當在Visual

C++環境中調試運行(按F5鍵)程式時,可以在Output視窗的Debug頁看到TRACE宏的輸出內容。實際上,TRACE宏是封裝了
Windows API函數OutputDebugString的功能,有些協助工具輔助可以在不驚動Visual
C++調試器的前提下,攔截程式中TRACE宏的輸出內容,比如《深入淺出MFC》的附錄中提到的Microsoft System
Journal(MSJ)1996年1月的C/C++專欄介紹的TraceWin工具(在較老版本的MSDN中可以找到原始碼和文檔)以及功能強大的免費
工具DebugView。

使用TRACE宏,我們可以輕鬆瞭解程式中各個函數之間的相互協作關係和被調用的先後順序和時間,進一步說,你能夠完全掌握程式的執行流程。

最後請注意,TRACE宏會對程式效率有所影響,所以,當前不用的TRACE宏最好刪除或者注釋掉。

4 閱讀程式的技巧

對於程式員來說,無論是學習還是工作,經常要閱讀其他程式員的原始碼,如何快速領悟程式的思想,洞悉程式的結構和各個組成部分的功能,進而全面掌握程式所涉及的方方面面,是程式員很重要的一項基本技能。下面介紹一些常用的技巧。

4.1 從功能、介面入手

一個完整的應用程式或者系統是由若干相對獨立的功能構成,這些功能反應在與使用者互動的圖形介面上,就是各種功能表命令、工具列按鈕命令等等。所以如果
當前只對程式的某幾個功能感興趣,可以在程式中找到這些功能表命令、按鈕命令等的ID響應函數,以此為起點,逐步深入到程式內部,直到完全理解該功能的實現
為止。此過程所花費的時間,很大程度上取決於程式員對調試技術的掌握程度。

需要強調的是,在不熟悉程式核心結構和實現技術的情況下,直接採用該方法探究程式,當逐步深入到程式核心時,涉及的程式模組數量會急劇增長,理解難度也會驟然增大;一旦你對程式核心結構和實現技術瞭然於胸,採用該方法探究程式,會有勢如破竹之感覺。

4.2 砍去枝葉,只留主幹

前面已經提到,無論如何,最終你都要掌握程式核心結構和實現技術。如何掌握呢?方法是首先將拿到的程式進行完整的備份,然後將次要功能都從程式中去
掉,只留下的必須的部分。去除次要功能是一個反覆多次的過程,花費的時間取決於程式員對行業知識的理解程度、編程技術的高低和經驗的多少。

經常遇到無法在短時間內判斷某個模組是否次要的情況(隨著對程式的理解逐漸加深,以及經驗和技術的積累,這種情況會越來越少),這時候建議直接將該模組去除,然後重新編譯串連程式,運行程式,看程式運行是否正常。

以上介紹的兩種方法是使用比較頻繁的,兩種方法可以相互結合,交替使用。但無論採用什麼方法探究閱讀程式,都不要指望能夠不費任何氣力,花費一兩個鐘頭就能夠將上萬行的程式探究個明白。

5 參考資料
《程式設計實踐》,機械工業出版社
《高品質C++編程指南》,林銳
《應用程式調試技術》,John Robbins,清華大學出版社
《物件導向軟體構造》,Bertrand Meyer,清華大學出版社

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.