很早前就想寫(xiě)點(diǎn)總結(jié)將編程中遇到的各種錯(cuò)誤刨根挖底地羅列出來(lái)。但是因?yàn)檫@些錯(cuò)誤(VC中開(kāi)調(diào)試器遇到的各種錯(cuò)誤對(duì)話框)都是隨機(jī)性的,真正想總結(jié)的時(shí)候又不想不起來(lái)有哪些錯(cuò)誤。恰好最近運(yùn)氣比較背,各種錯(cuò)誤都被我遇遍了,于是恰好有機(jī)會(huì)做個(gè)總結(jié)。
這里所說(shuō)的VC下的錯(cuò)誤對(duì)話框時(shí)指在VC中開(kāi)調(diào)試器運(yùn)行程序時(shí),IDE彈出的對(duì)話框。
1.不是錯(cuò)誤的錯(cuò)誤:斷言 .
將斷言視為錯(cuò)誤其實(shí)有點(diǎn)可笑,但是因?yàn)橛行┩瑢W(xué)甚至不知道這個(gè),所以我稍微提一下。斷言對(duì)話框大致上類似于:
斷言對(duì)話框是由assert引起的,在對(duì)話框上通常會(huì)給出表達(dá)式,例如assert( 0 ); 彈出對(duì)話框時(shí)就會(huì)將0這個(gè)表達(dá)式顯示出來(lái)(Expression:0)。關(guān)于assert的具體信息建議自己google。這里稍微提一下一個(gè)技巧:有時(shí)候?yàn)榱俗宎ssert提供更多的信息,我們可以這樣寫(xiě)一個(gè)assert:
assert( expression && "Function : invalid argument!" );
因?yàn)樽址挥迷诓紶柋磉_(dá)式中時(shí),始終為true,不會(huì)妨礙對(duì)expression的判斷,當(dāng)斷言發(fā)生時(shí)(expression為false) 時(shí),斷言對(duì)話框上就會(huì)顯示這個(gè)字符串,從而方便我們調(diào)試。
要解決這個(gè)問(wèn)題,首先要確定斷言發(fā)生的位置,如果是你自己設(shè)置的斷言被引發(fā),就很好解決,如果是系統(tǒng)內(nèi)部的函數(shù)產(chǎn)生的,那么一般是因?yàn)槟銈魅氲暮瘮?shù)參數(shù)無(wú)效引起。
2.內(nèi)存相關(guān):最簡(jiǎn)單的非法訪問(wèn):
C、C++程序中經(jīng)常誤用無(wú)效的指針,從而大致各種各樣的非法內(nèi)存訪問(wèn)(寫(xiě)/讀)。最簡(jiǎn)單的情況類似于:
這樣的情況由類似以下代碼引起:
char *p = 0;
*p = 'a';
當(dāng)你看到類似于“寫(xiě)入位置XXXX時(shí)發(fā)生訪問(wèn)沖突“時(shí),那么你大致可以斷定,你的程序在某個(gè)地方訪問(wèn)到非法內(nèi)存。開(kāi)調(diào)試器對(duì)調(diào)用堆棧進(jìn)行跟蹤即可找出錯(cuò)誤。
3.內(nèi)存相關(guān):不小心的棧上數(shù)組越界:
當(dāng)你寫(xiě)下類似以下的代碼時(shí):
char str[3];
strcpy( str, "abc" );
就將看到如下的對(duì)話框:
對(duì)話框大致的意思就是說(shuō)str周圍的棧被破壞了,因?yàn)閟tr本身就被放在棧上,所以strcpy(str,"abc")多寫(xiě)入的'\0'就寫(xiě)到非法的棧區(qū)域。看到這樣的對(duì)話框可以根據(jù)調(diào)用堆棧定位到錯(cuò)誤發(fā)生的函數(shù),然后檢查此函數(shù)內(nèi)部定義的數(shù)組訪問(wèn),即可解決問(wèn)題。
4.內(nèi)存相關(guān):不小心的堆上數(shù)組越界:
并不是每次數(shù)組越界都會(huì)得到上面所描述的錯(cuò)誤,當(dāng)數(shù)組是在堆上分配時(shí),情況就變得隱秘得多:
char *str = new char [2];
strcpy( str, "ab" ); //執(zhí)行到這里時(shí)并不見(jiàn)得會(huì)崩潰
delete [] str;//但是到這里時(shí)就肯定會(huì)崩潰
以上代碼導(dǎo)致的錯(cuò)誤對(duì)話框還要詭異些:
似乎不同的DAMAGE對(duì)應(yīng)的錯(cuò)誤號(hào)(這里是47)都不一樣,因?yàn)檫@里的錯(cuò)誤發(fā)生在delete,而delete跟new很可能在不同的地方,所以這個(gè)錯(cuò)誤調(diào)試起來(lái)不是那么容易,很多時(shí)候只能靠經(jīng)驗(yàn)。
當(dāng)看到類似的對(duì)話框時(shí),根據(jù)調(diào)用堆棧跟到delete時(shí),你就可以大致懷疑堆上數(shù)組越界。
5.調(diào)用相關(guān):函數(shù)調(diào)用約定帶來(lái)的錯(cuò)誤:
這是所有我這里描述的錯(cuò)誤中最詭異的一種,先看下對(duì)話框大致的樣子:
對(duì)話框大致的意思就是說(shuō)(沒(méi)開(kāi)調(diào)試器時(shí)對(duì)話框樣式可能不一樣),通過(guò)函數(shù)指針調(diào)用某個(gè)函數(shù)時(shí),函數(shù)指針的類型(函數(shù)原型)可能與函數(shù)指針指向的函數(shù)的類型不一樣。這里的類型不一致主要是調(diào)用約定(call conversation)不一樣。如果函數(shù)類型(參數(shù)個(gè)數(shù),返回值)不一樣,一般不會(huì)出錯(cuò)。
調(diào)用約定是指調(diào)用一個(gè)函數(shù)時(shí),函數(shù)參數(shù)的壓入順序、誰(shuí)來(lái)清理?xiàng)5膬?nèi)容等。例如默認(rèn)的C、C++調(diào)用約定__cdecl,對(duì)于函數(shù)的參數(shù)是從右往左壓入。而__stdcall(WIN API的調(diào)用約定)則是從左向右壓。我這里所說(shuō)的函數(shù)類型不一樣,就是指一個(gè)函數(shù)是使用__cdecl,還是__stdcall。例如以下代碼:
#include <iostream>

void __stdcall show( const char *str )



{

}

void __stdcall show2()



{

}

int main()



{

typedef void (*Func)( const char *);

void *p = show;

Func my_func = (Func) p;

my_func( "kevin" );

return 0;

}


因?yàn)镕unc默認(rèn)地被處理為_(kāi)_cdecl,而show是__stdcall的,所以當(dāng)通過(guò)函數(shù)指針my_func時(shí),就導(dǎo)致了以上對(duì)話框的出現(xiàn)。但是當(dāng)p指向show2時(shí),又不會(huì)出錯(cuò),這是因?yàn)閟how2沒(méi)有參數(shù),不同的調(diào)用約定不影響這個(gè)規(guī)則。
6.異常相關(guān):默認(rèn)終止程序
當(dāng)我們使用C++庫(kù)時(shí),因?yàn)閹?kù)本身可能會(huì)拋出C++異常,如果你不捕獲這個(gè)異常,那么C++默認(rèn)就會(huì)調(diào)用abort(或者exit)函數(shù)終止程序。例如:
void test()


{
throw std::exception( "some exceptions" );
}

當(dāng)你調(diào)用test函數(shù)時(shí),如果不catch這個(gè)異常,開(kāi)調(diào)試器就會(huì)得到類似的錯(cuò)誤對(duì)話框:
而如果不開(kāi)調(diào)試器,則會(huì)得到:

當(dāng)你看到類似于“This application has requested the Runtime to terminate it…”之類的字眼時(shí),那就表明程序調(diào)用了abort(或exit)函數(shù),導(dǎo)致程序異常終止。其實(shí)這個(gè)錯(cuò)誤只要開(kāi)調(diào)試器,一般可以準(zhǔn)確定位錯(cuò)誤的發(fā)生點(diǎn)。
7.VC運(yùn)行時(shí)檢查-未初始化變量
VC的調(diào)試器會(huì)對(duì)代碼進(jìn)行運(yùn)行時(shí)檢查,這可能會(huì)導(dǎo)致VC彈出對(duì)你看上去正確的代碼。這也許不是一個(gè)錯(cuò)誤。例如:
int test_var;
if( test_var == -1 )
{
test_var = 0;
}
test_var沒(méi)有初始化就進(jìn)行if判斷,當(dāng)運(yùn)行以上代碼開(kāi)調(diào)試器時(shí),就會(huì)得到如下對(duì)話框:
8.破壞的堆
VC對(duì)于在堆上分配的內(nèi)存都做了記錄,我想這主要用于free釋放內(nèi)存時(shí)做歸還處理。
char *p = (char*) malloc( 100 );
p += 10;
free( p );
當(dāng)執(zhí)行以上代碼時(shí),因?yàn)閜的值已經(jīng)改變,提交到free的指針值變化,VC就會(huì)給出以下錯(cuò)誤提示:
