Learning C++ 之1.11 debugging程序(步進和斷點)

語法和語義錯誤:

編程非常困難,有很多可能犯錯的地方。錯誤經常分爲幾類:語法錯誤和語義錯誤(邏輯錯誤)。

語法錯誤的發生往往是你沒有按照C++的標準規範來寫代碼。這包括類似於缺少分號,未定義的變量,不匹配的括號,比如下面的例子:

#include <iostream>; // preprocessor statements can't have a semicolon on the end
 
int main()
{
    std:cout < "Hi there; << x; // invalid operator (:), unterminated string (missing "), and undeclared variable
    return 0 // missing semicolon at end of statement
}

幸運的是編譯器可以準確的告訴你這些錯誤,你可以很簡單的分辨並解決這些問題。你需要重新編譯一遍,直到所有的錯誤修復。

有些時候程序會崩潰,如下面的例子:

#include <iostream>
 
int main()
{
    int a = 10;
    int b = 0;
    std::cout << a << " / " << b << " = " << a / b; // division by 0 is undefined
    return 0;
}

有些時候只會生成錯誤的值:

#include <iostream>
 
int main()
{
    std::cout << "Hello, word!"; // spelling error
    return 0;
}

或者:

#include <iostream>
 
int add(int x, int y)
{
    return x - y; // function is supposed to add, but it doesn't
}
 
int main()
{
    std::cout << add(5, 3); // should produce 8, but produces 2
    return 0;
}

不幸的是編譯器不能告訴你這一類的錯誤,因爲編譯器只能直到你寫了什麼,但是並不知道你的意圖。

在上面的例子裏,錯誤非常容易發現。但是在大多數重要的程序中,一些錯誤是沒有辦法通過眼睛來發現的。

所以這一類的錯誤就需要使用debugger來發現。

the debugger

debugger是一種程序,允許編程人員來控制並且試試顯示程序運行狀態的程序。

比如程序員可以使用debugger逐行地跟蹤程序定位,檢查函數的值。通過代碼的實際值和期望值做對比,或者觀察程序的實際執行路徑,debugger可以最大的發現程序的語義錯誤。

早期的debugger程序,如GDB,是通過命令行控制的,程序員需要敲命令來讓它工作。最近的調試器,如Borland的turbo調試器都是圖像化操作的了。幾乎所有的IDE都有內置的debugger程序,多以你就可以直接在你的編譯環境中來調試程序。

現在所有的IDE的調試器都有相同的調試環境以及方法,但是具體的菜單訪問方式以及快捷鍵的設置基本上沒有什麼一致性。儘管我們下面的例子使用的是Visual Studio 2005,當你使用其他的IDE的時候應該會重新熟悉你的debugger環境。

在開始之前,請確保你的code是debug環境。

步進:

步進的方式是可以讓你逐行地跟蹤代碼,這可以讓你跟蹤每一行代碼地執行,以觀察代碼是否按照你的要求工作。

實際上只有三個不同地命令:step into,step over,step out,我們會一步一步地學習。

step into:

step into就是執行下一行的代碼。如果這一行是一個函數調用,step into會直接跳進相應的函數的第一行。

讓我們看一個非常簡單的程序:

#include <iostream>
 
void printValue(int nValue)
{
    std::cout << nValue;
}
 
int main()
{
    printValue(5);
    return 0;
}

你應該知道,我們的程序是從main函數開始執行的。因爲我們想要在main函數裏面調試,所以在main裏面使用step into。在Visual Stadio裏面你可以在調試欄裏面找到逐語句,然後調試,或者直接使用F11.

使用其他的IDE你也可以找到類似的調試->逐語句,然後調試。

當你選擇完成之後會有兩件事情發生。首先因爲我們的程序是一個控制檯程序,所以會跳出一個控制檯。這個控制檯應該是空的,因爲我們沒有輸出任何東西。其次你應該看到一個標記性的東西,如箭頭之類的。


這箭頭表示該行程序將在下一行執行。在這個例子裏,表示下一步執行的程序是main函數的{,再次選擇逐步調試,或者F11,箭頭就會走到下一行,執行PrintValue函數。


這意味着函數將會走到下一行的函數中,就會走到PrintValue中的{。


繼續執行step into就會走到std::cout裏面,這個時候選擇step over,這將執行這個語句,並且不用執行<<操作符。這個時候你就能看到5已經輸出在控制檯了。

這次選擇step over,你會發現下一步不會調到std::cout裏面。由於cout已經執行,你就看到了5輸出在屏幕上了。

再次選擇step into,你就會發現箭頭已經直到main函數裏了。


這個意思是調式已經調用到PrintValue的返回了 。

step over:

就像step into,step over也是逐步執行的。如果碰到一個函數,step over會直接跳過這個函數,直接把函數的執行結果返回給你。

讓我們用相同的程序看一下這個例子:

#include <iostream>
 
void printValue(int nValue)
{
    std::cout << nValue;
}
 
int main()
{
    printValue(5);
    return 0;
}

step into函數調用的地方,然後直接step over,就會發現直接跳過該函數了,函數已經執行完成。

step over已經提供了一種簡單的直接濾過函數的功能,調試更加方便。

step out:

不像step into和step over,step out直接略過剩下的所有code,並把調試權交給你。

具體看下面相同的例子:

#include <iostream>
 
void printValue(int nValue)
{
    std::cout << nValue;
}
 
int main()
{
    printValue(5);
    return 0;
}

step into函數裏面,直到進入printValue函數:


這個時候直接step out,你會發現直接跳過剩餘代碼,執行到最後。

執行到光標處:

雖然步進調試可以確認你遇到的所有問題,但是在一個很大的程序中使用這種方法非常費時間。

幸運的是,現在的debugger工具可以提供更好的方法來定位問題。

比較有效的一個方法是執行到光標處,這個命令可以讓代碼執行到光標所在處,並且調試完成,下面用同一個例子看一下:

#include <iostream>
 
void printValue(int nValue)
{
    std::cout << nValue;
}
 
int main()
{
    printValue(5);
    return 0;
}

把光標放到std::cout...這裏,然後右鍵點擊,選擇執行到光標處。

你會發現代碼直接執行到了你的光標所在處。

執行:

一旦你在執行debugger的程序中,你可以直接告訴程序執行到最後或者程序的斷點處。在vs中這個命令叫做continue,其他的地方叫做go或者run。如果你還是用上面的例子,你step into print Value的函數的時候,你可以執行run環境,這樣程序就執行到最後了。

斷點:

最後我們來探討一下斷點,斷點是一個特殊的標記,告訴編譯器執行的過程中停止編譯。設置斷點你可以在dubug中選擇設置斷點,也可以直接雙擊側邊欄,效果如下:


首先通過step into來進入調試模式,然後點擊執行。你會看到程序執行到斷點處就停止了。


當你需要驗證一段特殊的代碼的時候,斷點是非常有用的。簡單的設置一下斷點,程序每次運行到這個地方都會停止,這樣方便定位問題。

最後的建議:直到現在我們都是通過step into來開始進行調試的。然而有必要告訴編譯器直接運行到最後,在VS中,可以直接選擇開始調試。這樣就可以有效地減少你的輸入命令。

結論:

恭喜你已經瞭解了所有的調試你寫的代碼的方法了。然而這只是讓debugger好好工作的一半內容。下一章將會介紹怎麼檢查編譯的值,以及怎麼使用額外的兩個窗口調試程序。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章