电竞比分网-中国电竞赛事及体育赛事平台

分享

Vc6.0 調(diào)試指南

 angelbrian 2012-08-24
Vc6.0 調(diào)試指南 --Happy Debugging

Vc6.0 調(diào)試指南 --Happy Debugging
回去洗個澡,再來完成這項浩大的工程。

//Vc6.0下載 http://down./100.html
//Visual AssistX //Vc6.0的一個好用的插件 http://down./361.html
//如果您 Vc6.0 還不知道怎么使用, 請點擊 http://down./357.html 下載 Pdf 文檔 自行學(xué)習(xí)。

/*****************************************
一:深入了解 編譯、鏈接、組建(Look into Compile、Linking、Build)
(1) Compile
(2) Linking
(3) Build

二:斷點 (Break Point)
(1) 普通斷點 (Nomal Break Point)
(2) 條件斷點 (Condition Break Point)
(3) 數(shù)據(jù)斷點 (Data Break Point)

三:斷點之后能做些什么?(What can I do after Break Point)
(1)variables
(2)watch
(3)stack
(4)memory

四:斷言 (Assert)

五:printf()

六:Log

七:Trace

八:虛擬內(nèi)存簡介(Virtual Memory Intro )

九:常見的段錯誤 (Common Segmentation Fault)
(1)堆區(qū)內(nèi)存錯誤 (Heap Memory Errors)
1,未初始化的內(nèi)存仿問 (Uninitialized Memory Access)
2,無效的內(nèi)存仿問 (Invalid Memory Access)
3,內(nèi)存泄露 (Memory leaks)
4,未分配內(nèi)存 (Missing allocation)

(2)棧區(qū)內(nèi)存錯誤 (Stack Memory Errors)
1,未初始化的內(nèi)存仿問 (Uninitialized Memory Access)
2,無效的內(nèi)存仿問 (Invalid Memory Access)
3,數(shù)組越界 (Writing off the end of the array)
4,棧溢出 (Stack Overflows)

十:輕松解決 內(nèi)存泄漏 (Hunting Memory Leaks)

結(jié)束語:怎樣盡可能的避免錯誤
**********************************************************/

下面就開始祥細講解,/

一:深入了解 Compile、Linking、Build
(1)Compile - 編譯
當(dāng)您點擊 編譯按鈕時,編譯器將會把你的源代碼文件 (.c文件)轉(zhuǎn)換為目標(biāo)文件(.obj文件) ,目標(biāo)文件包含的是
源代碼文件翻譯后的機器語言。這些是不能被直接運行的,還需要 鏈接器將此中間代碼與其他代碼相結(jié)合來生成可執(zhí)行文件。請轉(zhuǎn)看 Linking,

Compile時,編譯器通常會給你2種類型的提示:warnings 和 errors

warnings
別小看 warnings ,它有可能會導(dǎo)致相當(dāng)嚴重且極其隱蔽的 bug,尤其是在 指針管理內(nèi)存 這一塊,/

常見的warning有以下幾種類型

1,使用了未經(jīng)初始化的變量,或者定義變量了卻沒有使用。
解析: 未經(jīng)初始化的變量會 存一個隨機值,絕大多數(shù)的時候這個值都不是你想要的,你用它,編譯器能不給你warning嗎,?

2,使用了一些看上去非常愚蠢的語句,編譯器都看不下去了
例如, if (blueguy = 0)
           printf("blueguy = 0!!");

       if(blueguy && greengirl || hemy)
           ;

3, 使用了未定義的語句 (注意,vc6.0是不會給這樣的語句一個warning的)

例如, j = i++ + i++;  //我自己都不知道自己想表達什么意思 , 呵呵
       x = x>0 ? x++ : x--;

4,類型不匹配
例如, char * blueguy = (int*) greengirl;
本意是按單字節(jié)仿問內(nèi)存的,結(jié)果卻按四字節(jié)仿問內(nèi)存, 你感到崩潰,我感到崩潰,編譯器也感到崩潰,估計編譯器會真的崩潰了 ,/

5, 函數(shù)原型明明寫著有返回值的,結(jié)果函數(shù)體內(nèi)卻沒有 return一個值, 反之亦然。
例如,
int main(void)
{
}
或者
void main()
{
    return 0;
}

......等等,等等,等等。/
好了,warnings 就簡單介紹到這里了,希望您寫的程序里 一個 warning也沒有

errors
出現(xiàn)errors時,相對來說比較好解決一些, 通常編譯器會給你明確的提示
像,"syntax errors", "unexpected parenthesis ", "unexpected end of file"之類的,

常見的errors有以下幾種類型
(1)語句缺少 ";"號
例如,
for(;)

struct bluguy
{
   int x;
}


(2)括號不匹配
例如,
int main(void)
{

if (!blueguy

Compile就這樣結(jié)束了,下面接著看 Linking

(2)Linking - 鏈接
vc6.0上是沒有 Linking按鈕的,或許是 我菜了,/ 沒注意到
vc6.0的 Build 把 Compile與Linking合在一起了 ,/
鏈接的作用是將目標(biāo)代碼、系統(tǒng)的標(biāo)準(zhǔn)啟動代碼和庫代碼結(jié)合在一起,生成可執(zhí)行程序。
在你Compile的時候,編譯器假定所有的結(jié)構(gòu)體、函數(shù)、全局變量都已經(jīng)在別的文件里聲明了,但這個假設(shè)并不總是成立的,
鏈接器就是在文件中查看這些結(jié)構(gòu)體、函數(shù)以及全局變量是不是已經(jīng)聲明了,/

常見的Linker Errors有以下幾種,
1, "undefined function" - 不明確的函數(shù) (這可能是函數(shù)參數(shù)不匹配 或者未包含相應(yīng)的庫 或者函數(shù)沒有函數(shù)原型造成的,/)

2,"could not find definition for X" -  使用的變量未定義,

3,"multiple definitions"  - 多重定義(多個文件定義了相同的函數(shù)或全局變量)

(3)Build - 組建
Build沒什么好講的,就是集成了Compile與Linking 的功能。
將Compile與Linking 分隔開來 ,可以讓你能夠單獨編譯文件,總之是為了方便管理的,/

順便說一下,當(dāng)您的程序出現(xiàn)了,warning 或者 Errors 的時候,雙擊一下提示信息就可以定位到那一行,/

二:斷點
(1) 普通斷點,
圖1


nomal.jpg (60.42 KB)



普通斷點是最簡單, 也是最常用的,只要在能夠下斷點的位置下上斷點(按下F9,有的行是不能下斷點的),上圖中,斷點下在 blueguy = 0;這條語句上,也就是第9行,以下簡稱"第9行",好,按下 F5, 程序立馬就斷在 blueguy = 0; 這條語句上。斷下來有什么用?請?zhí)D(zhuǎn)本文列表三:斷點之后能做些什么?

(2) 條件斷點1 - 遇到斷點一定次數(shù)后斷下來

還是在圖1的第9行下個斷點,然后,按下 Ctrl+b, 彈出如下對話框
圖2


condition1.jpg (32.56 KB)


單擊,at{"blueguy.c"}.9 這一行后,彈出如下對話框
圖3


condition2.jpg (39.05 KB)



好,現(xiàn)在單擊 Condition按鈕,彈出如下對話框
圖4


condition3.jpg (50.53 KB)


接下來, 在stopping標(biāo)識的文本框內(nèi)填上一個你想要填上的數(shù)字,這里填的是 6。好,
單擊 OK按鈕 ,再次按下 F5, 程序斷在了第9行。此時按下 Ctrl+b 可以看到
圖5


condition4.jpg (34.93 KB)



(2) 條件斷點2 - 某個變量(普通變量或指針變量)的值發(fā)生變化時斷下來
還是在第9行下上斷點, 先按下F5,程序斷在了第9行,按下 Ctrl+b
彈出如下對話框


condition2.jpg (39.05 KB)


再按下 Condition按鈕,彈出如下對話框



condition3.jpg (36.24 KB)


在Enter the expression to be evaluated標(biāo)識的文本框下
寫上你想寫入的變量,這里寫的是 blueguy, 再按下 F5, 好,程序斷在了第9行,并伴有下圖提示


condition5.jpg (24.97 KB)



(3)數(shù)據(jù)斷點 - 某個表達式的值為真時斷下來
還是在第9行下上斷點, 先按下F5,程序斷在了第9行,按下 Ctrl+b


condition2.jpg (39.05 KB)


單擊下Date按鈕,彈出如下對話框,


data1.jpg (41.11 KB)


在Enter the expression to be evaluated標(biāo)識的文本框下
寫上你想寫的表達式,這里寫的是 greengirl == 6
好,先按下F9撤消斷點,再按下F5, 程序斷在了第9行,伴有下圖提示:
見下樓,


data2.jpg (33.24 KB)


如果你嫌麻煩的話,你也可以這樣下斷點
if (greengirl == 6)
  blueguy = 0;
不過這樣做,調(diào)試過后你得記得刪除它,否則會給閱讀代碼選成 視覺障礙,

好了,怎樣下斷點就講到這里,如果你細心的話,會發(fā)現(xiàn)還有個 Message按鈕, 那是消息斷點,不怎么常用,
不在我的講解之列 ,/


三:斷點之后能做些什么?
先上張大圖


debug.jpg (146.13 KB)



假設(shè)程序斷在了第9行
(1)variables
這個窗口可以查看 自動變量的值,不過他的最大用途在于查看函數(shù)的返回值。
假設(shè) 有這樣一個程序段
#include <stdio.h>

int blueguy(void){return 5;}
int sum(int a, int b){return a+b;}

int main(void)
{
    printf("%d", sum(5, blueguy()));
    return 0;
}
如果,我想查看 sum() 以及 blueguy()的返回值,如果不使用 variables窗口,
我就得 定義兩個變量來接收 sum() 以及 blueguy()的返回值,例似這樣的語句,int greengirl = blueugy().... 是不是? 很幸運, variables窗口為我們省去了這個麻煩。

現(xiàn)在我們把斷點下在 return 0; 按下 F9, 程序斷下來。
看下面這張圖, 很驚喜吧,


Autos.jpg (59.43 KB)



(2)watch
點擊調(diào)試工具條上的 watch按鈕
1,現(xiàn)在想查看下greengirl的值,只需把光標(biāo)放在 greengirl上選中,拖到 watch窗口里就行了

2,watch窗口中,在整形變量后面加上",c"可以顯示該變量對應(yīng)的ASCII字符,也可以直接敲數(shù)字這么顯示,比如118,c的對應(yīng)值是'v', 'v',d就是顯示字符'v'對應(yīng)的十進制ASCII碼值 是118, 'v',x顯示的是對應(yīng)的十六進制的ASCII碼值

3, 數(shù)組名后加上",N"  (N表示表示數(shù)組元素個數(shù)) 可以查看該數(shù)組的值, 對于查看大型數(shù)組的尾部數(shù)據(jù)比較方便
例如, int blueguy[100][10] = {...};
watch窗口中, 輸入blueguy[99], 10, 可以看到二維數(shù)組最后一維的數(shù)據(jù)。

4, watch可以計算簡單算式的值,例如: blueguy - greengirl

(3)stack
點擊調(diào)試工具條上的 stack按鈕

假設(shè)現(xiàn)在內(nèi)存崩了,且崩在了第9行,stack 顯示了函數(shù)的調(diào)用關(guān)系, 可以逐級向上檢查錯誤

(4)memory
點擊調(diào)試工具條上的 memory按鈕

memory窗口可以查看指針?biāo)傅膬?nèi)存區(qū)域里的值,使用時,把指針的值放在Address里就可以了,這在查看聲音、圖片、文件等大型數(shù)組的值時相當(dāng)方便

(5)disassembly
有時內(nèi)存崩了,彈出這個反匯編窗口,在這個窗口里可以看到相應(yīng)的函數(shù),不過用途不大。
還有兩個不常用的窗口, 不在我的講解之列

斷點之后能做些什么?就簡單的介紹到這里了,反正這些都是很常用的,具體怎么用不在我的講解之列


四:斷言
使用斷言時,先加上 頭文件, <assert.h>
斷言是在條件表達式不成立的情況下,終止程序。
斷言是用來調(diào)錯的,不要用來作為異常處理語句

假設(shè) 現(xiàn)在內(nèi)存崩了,并且程序中有如下代碼
char *blueguy = malloc(10000);
我現(xiàn)在想看看 是不是blueguy分配失敗了,
可以這樣寫個 斷言
char* blueguy = malloc(blueguy);
assert(blueguy);

現(xiàn)在假設(shè) blueguy 分配失敗,為空,那么編譯器就會彈出類似下圖的提示框


assert.jpg (32.26 KB)



這個窗口上面寫著 錯誤在哪一個文件,哪一行  ,此時找到那個文件,按下 ctrl+g,在框內(nèi)填上行數(shù)就可以定位到那一行。/

五: printf()
現(xiàn)在講解下深受控制臺下編程的朋友鐘愛的printf()函數(shù); 但請注意,不要迷戀 printf();因為他的功能并不是那么強大,想查看某個值的時候,斷個點就可以,不用花力氣去書寫 printf()輸出語句。

不過printf()也有它的優(yōu)點,想查看變量之外的信息的時候,還是很有用的
比如說,我想看下 漢諾塔 遞歸程序的 函數(shù)調(diào)用路徑
可以 定義一個 監(jiān)視變量 表示遞歸深度 ,/

Example:
//聲明:本人不保證程序能夠正確運行,
#include <stdio.h>

void Hanoi(int n, char a, char b, char c)
{
    if ( n == 1 )
    {
        while(n--)
           printf(" ");
        printf("Hanoi(%d, %c, %c, %c)\n", n, a, b, c);

        //cout << a << "->" << b << endl;
        //printf("%c -> %c\n", a, b);
        return ;
     }

     while(n--)
       printf(" ");
    printf("Hanoi(%d, %c, %c, %c)\n", n, a, b, c);

//先將n-1個盤子,以b為中轉(zhuǎn),從a柱移動到c

    Hanoi( n-1,a,c,b);
//將一個盤子從a移動到b
    //cout << a << "->" << b << endl;
    //printf("%c -> %c\n", a, b);

//將c柱上的n-1個盤子,以a為中轉(zhuǎn),移動到b柱
    Hanoi( n-1,c,b,a);

}

int main(void)
{
    int N;
    scanf("%d", &N);
    Hanoi(N,'A','B','C');
    return 0;
}

當(dāng)然,printf()還有其他的用途,關(guān)鍵看你怎么用了,不在我的講解之列

六:Log
有時候,前面介紹的調(diào)試技術(shù)(本文已經(jīng)通過 編程中國ISO9001認證)并不能直觀的看到數(shù)據(jù)的變化,比如 watch窗口太小了,放不下那么多的值...
Log是java/c#中的名詞,c 語言是沒有提供這個調(diào)試功能的,實際上所謂的Log也沒什么東西,無非是打開文件,寫入數(shù)據(jù)罷了。
我們可以用 c語言 的 fopen() 、fwrite ()等文件操作函數(shù)來仿寫 Log

這里要介紹兩個用于調(diào)試的重要的宏
__FILE__  表示 文件名
__LINE__  表示調(diào)用語句所在的行數(shù)

Example:
//聲明:本人不保證程序能夠正確運行,
void Log(void)
{
   char bugInfo[200];
   pFile = fopen ("E:\\blueguyDebug.log",  , "a+" );
   sprintf(bugInfo, "File: %s  Line: %d: Here is a bug", __FILE__, __LINE__);
   fwrite (bugInfo, sizeof(bugInfo[0]) , sizeof(bugInfo) , pFile );
   fclose (pFile);
}

/*一套完整的日志接口*/

void bgOpenLog(void)
{

     if(Log == NULL)
         Log = fopen("log.txt", "w");
}

void bgWriteLog(const char* s)
{
    fwrite(s, sizeof(char), strlen(s), Log);
}

void bgCloseLog(void)
{
     fclose(Log);
     Log = NULL;
}

不再多說了,繼續(xù)看下一個調(diào)試技術(shù)


七:Trace

假設(shè)你現(xiàn)在 在寫windows應(yīng)用程序,  程序出錯了,你想用printf()函數(shù)來輸出信息, 很遺憾的告訴你,可能沒有,
這個時候,您可以使用 可變參數(shù)來仿寫 printf()函數(shù)

Example:
//聲明:本人不保證程序能夠正確運行,

Trace(const char* fmt,...)
{
     char text[256];  
     va_list  ap;         
     if (fmt == NULL)         
         return;         
     va_start(ap, fmt);         
     vsprintf(text, fmt, ap);     
     va_end(ap);
     
     //在屏幕上畫出這個字符串
}

八:虛擬內(nèi)存簡介 (Virtual Memory)
每一個跑在你的操作系統(tǒng)的應(yīng)用程序都有一個唯一的地址空間。這些地址空間看起來是連續(xù)的內(nèi)存塊,實事上,它們并不是物理上連續(xù)的內(nèi)存,僅僅是操作系統(tǒng)給應(yīng)用程序的一個鏡像空間--虛擬內(nèi)存.

每個應(yīng)用程序可利用的虛擬內(nèi)存一般劃分為六個段:

環(huán)境變量段 -- 用于存儲環(huán)境變量和命令行參數(shù)。
棧 -- 用于存儲函數(shù)參數(shù),自動變量等。
堆 -- 用于動態(tài)分配內(nèi)存
兩個數(shù)據(jù)段 -- 分別用于存儲 初始化和未初始化的 靜態(tài)變量或全局變量
文本段 -- 用于存放實際的代碼



九:常見的段錯誤 (Common Segmentation Fault)
(1)堆區(qū)內(nèi)存錯誤 (Heap Memory Errors)
1,未初始化的內(nèi)存仿問 (Uninitialized Memory Access)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
    char *pStr = (char*) malloc(512);
    char c = pStr[0]; // the contents of pStr were not initialized
}

2,無效的內(nèi)存仿問 (Invalid Memory Access)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
    char *pStr = (char*) malloc(25);
    free(pStr);
    strcpy(pStr, "blueguy")// Invalid write to deallocated memory in heap
}


3,內(nèi)存泄露 (Memory leaks)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
    char *pStr = (char*) malloc(512);
    return;
}

4,未分配內(nèi)存 (Missing allocation)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
    char* pStr = (char*) malloc(20);
    free(pStr);
    free(pStr); // results in an invalid deallocation
}


(2)棧區(qū)內(nèi)存錯誤 (Stack Memory Errors)
1, 無效的內(nèi)存仿問 (Invalid Memory Access)
#include <stdio.h>
int* blueguy(void);
int main(void)
{
   
    int *greengirl = NULL;
    greengirl  = blueguy();
    printf("%d", greengirl[0]);
    return 0;     
}
int* blueguy(void)
{
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
    return a;
}

2,未初始化的內(nèi)存仿問 (Uninitialized Memory Access)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
    int a;
    int b = a * 4; // uninitialized read of variable a

}

3,數(shù)組越界 (Writing off the end of the array)
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
    int blueguy[10];
   
    printf("%d", blueguy[10]);   
    return 0;
}

4,棧溢出 (Stack Overflows)
//輸入了小于0的值
#include <stdio.h>
int factorial(int n);
int main(void)
{
    int blueugy, greengirl ;
    scanf("%d", &blueguy);
    greengirl = factorial(blueguy);     
    printf("%d", greengirl);
    return 0;   
}

int factorial(int n)
{
    if(n == 0)
    {
        return 1;
    }
    return factorial(n-1) * n;
}

//超過棧內(nèi)存分配限制
#include <stdio.h>
int factorial(int n);
int main(void)
{
    int blueugy[1000000];
   
    return 0;   
}

十:輕松解決 內(nèi)存泄漏(Hunting Memory Leaks)
為了解釋方便,假設(shè)現(xiàn)在程序需要載入500張圖片。
給圖片結(jié)構(gòu)體 定義一個  id;
typedef struct bgImage
{
   int id;
   char* buffer;
   int width;
   int height;
}BGImage;
typedef BGImage* Image;

定義一個 int inspect[500] (初始化為 0)數(shù)組來監(jiān)視內(nèi)存。
給每個文件名編號,從100開始,(之所以不從0開始編號,是為了處理方便),然后101,...

(1)
每次載入圖片的時候,根據(jù)路徑名計算出 圖片id。
bgMalloc()
{
    ID = (path[0]-'0')*100 + (path[1]-'0')*10 + (path[2]-'0');
    img->id =  ID;   
    inspect[img->id - 100]++;
    for (i = 0; i < 500; i++)
    {
        if (inspect[i] > 1)
           printf("內(nèi)存泄漏");
    }

(2)
每次釋放圖片的時候
bgFree(Image img)
{
    inspect[img->id - 100]--;
    free(img->buffer);
    img->buffer = NULL;
    free(img);
    img = NULL;
}

(3)
在某個時刻,根據(jù) 監(jiān)視列表 判斷內(nèi)存是否泄漏。比如程序退出的時候。
for (i = 0; i < 500; i++)
{
    if (inspect[i] > 0)
       printf("內(nèi)存泄漏");
}


小帖士:
(1)定義指針變量時就將其賦值為 NULL
(2)malloc/free, fopen/fclose使用時一定要配對,
(3)free/fclose后,記得將指針置為 NULL

至此, 我所知道的調(diào)試技術(shù)已經(jīng)介紹完了,這里列舉的都是常見的,也是常用的,怎樣去靈活運用這些調(diào)試技能,還是得靠自己多實踐, 記得某位哲人說過: 實踐是檢驗真理的唯一標(biāo)準(zhǔn)
又記得某位哲人說過:沒有調(diào)查就沒有發(fā)言權(quán)

結(jié)束語:怎樣盡可能的避免錯誤

調(diào)試的最高境界就是不去調(diào)試,怎樣才能不去調(diào)試或者只需少量調(diào)試就能使程序正常跑起來呢?
答案是,深入學(xué)習(xí) c語言,培養(yǎng) c語言 的審美感。怎么說 c語言也是一門語言,話都說不周整的人,
編程能力肯定不怎么樣,/君不見,那些大牛們寫起帖子來就像是寫論文,像RockCarry、Knocker、forever74、jig、 rootkit 、rtgirl、starwing83、廣陵絕唱..../ (不再一一列舉了)他們寫的帖子,讀起來就是不一樣,像是讀散文,/
哥最看不上的就是那些只會寫兩個字還讓別人回去琢磨的"高手"...

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多