C/C++使用過(guò)程中的溢出問(wèn)題詳解
內(nèi)容:在C/C++程序里有一類(lèi)非常典型的問(wèn)題,那就是:溢出問(wèn)題?,F(xiàn)在分別來(lái)分析一下常見(jiàn)的數(shù)組溢出,整數(shù)溢出,緩沖區(qū)溢出,棧溢出和指針溢出等。
1、數(shù)組溢出
在C語(yǔ)言中,數(shù)組的元素下標(biāo)是從0開(kāi)始計(jì)算的,所以,對(duì)于n個(gè)元素的數(shù)組a[n], 遍歷它的時(shí)候是a[0],a[1],...,a[n-1],如果遍歷到a[n],數(shù)組就溢出了。
void print_array(int a[], int n) { for (int i = 0; i < n; i++) { a[i] = a[i+1];//當(dāng)i = n-1時(shí),就發(fā)生了數(shù)組越界 printf(“%d\n”, a[i]); } }
上面的循環(huán)判斷應(yīng)該改為:
for (int i = 0; i < n-1; i++)
2、整數(shù)溢出
整數(shù)的溢出分為下溢出和上溢出。比如,對(duì)于有符號(hào)的char(signed char)類(lèi)型來(lái)說(shuō),它能表示的范圍為:[-128,127]之間;而對(duì)于無(wú)符號(hào)的char(unsigned char)來(lái)說(shuō), 它能表示的范圍為:[0,255]。
那么,對(duì)于下面的代碼:
signed char c1 = 127; c1 = c1+1;//發(fā)生上溢出,c1的值將變?yōu)?128 signed char c2 = -128; c2 = c2-1;//發(fā)生下溢出,c2的值將變?yōu)?27 unsigned char c3 = 255; c3 = c3+1;//發(fā)生上溢出,c3的值將變?yōu)? unsigned char c4 = 0; c4 = c4-1;//發(fā)生下溢出,c4的值將變?yōu)?55
從上面的例子可以看出,當(dāng)一個(gè)整數(shù)向上溢出,將會(huì)變?yōu)樽钚≈?,而向下溢出,將?huì)變?yōu)樽畲笾怠?/p>
來(lái)看下面的溢出代碼,該代碼負(fù)責(zé)提供一個(gè)小寫(xiě)字母轉(zhuǎn)換表,但存在一個(gè)整數(shù)溢出問(wèn)題:
void BuildToLowerTable( void ) /* ASCII版本*/ { unsigned char ch; /* 首先將每個(gè)字符置為它自己 */ /*ch為unsigned char,無(wú)符號(hào)數(shù),當(dāng)ch值為UCHAR_MAX, ch++將會(huì)發(fā)生向上溢出,變?yōu)?,導(dǎo)致循環(huán)無(wú)法退出。*/ for (ch=0; ch <= UCHAR_MAX;ch++) chToLower[ch] = ch; /* 將大寫(xiě)字母改為小寫(xiě)字母 */ for( ch = ‘A'; ch <= ‘Z'; ch++ ) chToLower[ch] = ch +'a' – ‘A'; }
該代碼負(fù)責(zé)在內(nèi)存中查找指定的字符ch,但也存在一個(gè)溢出問(wèn)題
void * memchr( void *pv, unsigned char ch, size_t size ) { unsigned char *pch = (unsigned char *) pv; /*當(dāng)size的值為0的時(shí)候,由于size是無(wú)符號(hào)整數(shù),因此會(huì)發(fā)生下溢出,變?yōu)橐粋€(gè)最大的整數(shù) 循環(huán)也將無(wú)法退出*/ while( -- size >=0 ) { if( *pch == ch ) return (pch ); pch++; } return( NULL ); }
3、緩沖區(qū)溢出
緩沖區(qū)溢出一般是調(diào)用了一些不安全的字符串操作函數(shù)比如:strcpy,strcat等(這些字符串操作函數(shù)在拷貝或者修改目標(biāo)位置的時(shí)候,并不判斷長(zhǎng)度是否會(huì)超過(guò)目標(biāo)緩存),或者設(shè)置參數(shù)超過(guò)了目標(biāo)緩存能容納的大小而造成的溢出問(wèn)題。
void func1(char* s) { char buf[10]; /*此時(shí),buf只有10個(gè)字節(jié),如果傳入的s超過(guò)10個(gè)字節(jié),就會(huì)造成溢出*/ strcpy(buf, s); } void func2(void) { printf("Hacked by me.\n"); exit(0); } int main(int argc, char* argv[]) { char badCode[] = "aaaabbbb2222cccc4444ffff"; DWORD* pEIP = (DWORD*)&badCode[16]; *pEIP = (DWORD)func2; /*badCode字符串超過(guò)了10個(gè)字節(jié),傳遞給func1會(huì)造成棧上緩沖區(qū)溢出 而且,由于badCode經(jīng)過(guò)精心構(gòu)造,在溢出的時(shí)候,根據(jù)函數(shù)的調(diào)用約定規(guī)則,會(huì)覆蓋棧上的返回地址, 指向了func2。所以,在func1退出的時(shí)候,會(huì)直接調(diào)用func2 */ func1(badCode); return 0; }
4、棧溢出
無(wú)論是內(nèi)核棧,還是應(yīng)用層的棧,都是有一定大小限制的。如果在棧上分配的空間大于了這個(gè)限制,就會(huì)造成棧大小溢出,破壞棧上的數(shù)據(jù)。比如局部變量過(guò)多,或者遞歸調(diào)度嵌套太深都會(huì)造成棧溢出。比如:
int init_module(void) { char buf[10000]; //buf[]分配在棧上,但10000的空間超過(guò)了棧的默認(rèn)大小8KB。 //所以發(fā)生溢出 memset(buf,0,10000); printk("kernel stack.\n"); return 0; } void cleanup_module(void) { printk("goodbye.\n"); } MODULE_LICENSE("GPL"); //應(yīng)用棧的大小對(duì)少??jī)?nèi)核棧的大小多少?什么時(shí)候容易棧溢出?
5、指針溢出
一塊長(zhǎng)度為size大小的內(nèi)存buffer,buffer的首地址為p,那么buffer最后一個(gè)字節(jié)的地址:
p+size-1,而不是p+size。如果寫(xiě)成了p+size,就會(huì)造成溢出,比如下面的代碼:
void* memchr( void *pv, unsigned char ch, size_t size ) { unsigned char *pch = ( unsigned char * )pv; unsigned char *pchEnd = pch + size; while( pch < pchEnd ) { if( *pch == ch ) return ( pch ); pch ++ ; } return( NULL ); }
上面的代碼用于查找內(nèi)存中特定的字符位置。對(duì)于其中的while()循環(huán),平時(shí)執(zhí)行似乎都沒(méi)有任何問(wèn)題。但是,考慮一種特別情況,即pv所指的內(nèi)存位置為末尾若干字節(jié),那么因?yàn)閜chEnd = pch+size,所以pchEnd指向最后一個(gè)字符的下一個(gè)字節(jié),將會(huì)超出內(nèi)存的范圍,即pchEnd所指的位置已經(jīng)不存在。
知道了問(wèn)題所在,那么可以將內(nèi)存的結(jié)尾計(jì)算方式改為:
pchEnd = pv + size – 1; while ( pch <= pchEnd ) { if( *pch == ch ) return ( pch ); pch ++ ; }
pchEnd指向了最后一個(gè)字節(jié)。但是,檢查循環(huán)內(nèi)部的執(zhí)行情況可知,由于pch每增加到pchEnd+1時(shí),都會(huì)發(fā)生上溢。因此,循環(huán)將無(wú)法退出。 于是,可以將程序修改為下面的代碼。將用size變量來(lái)控制循環(huán)的退出。這樣就不會(huì)存在任何問(wèn)題了。
void *memchr( void *pv, unsigned char ch, size_t size ) { unsigned char *pch = ( unsigned char * )pv; while( size -- > 0 ) { if( *pch == ch ) return( pch ); pch ++; } return( NULL ); }
大家知道,--size的效率一般比size--的效率高。那么是否可以將循環(huán)的判斷條件改為下面的語(yǔ)句呢?
while( --size >= 0 )
……
實(shí)際上這是不行的。因?yàn)楫?dāng)size=0時(shí),由于size是無(wú)符號(hào)數(shù),那么它將發(fā)生下溢,變成了size所能表示的最大正數(shù),循環(huán)也將無(wú)法退出。
6、字符串溢出
我們已經(jīng)知道,字符串是'\0'結(jié)尾的。如果字符串結(jié)尾忘記帶上'\0',那么就溢出了。注意,strlen(p)計(jì)算的是字符串中有效的字符數(shù)(不含’\0’)??疾煜旅婵截愖址拇a,看看有什么問(wèn)題沒(méi)呢?
char *str = “Hello, how are you!”; char *strbak = (char *)malloc(strlen(str)); if (NULL == strbak) { //處理內(nèi)存分配失敗,返回錯(cuò)誤 } strcpy(strbak, str);
顯然,由于strlen()計(jì)算的不是str的實(shí)際長(zhǎng)度(即不包含’\0’字符的計(jì)算),所以strbak沒(méi)有結(jié)束符’\0’,而在C語(yǔ)言中,’\0’是字符串的結(jié)束標(biāo)志,所以是必須加上的,否則會(huì)造成字符串的溢出。所以上面的代碼應(yīng)該是:
char *str = “Hello, how are you!”; char *strbak = (char *)malloc(strlen(str)+1); if (NULL == strbak) { //內(nèi)存分配失敗,返回錯(cuò)誤 } strcpy(strbak, str);
到此這篇關(guān)于C/C++使用過(guò)程中的溢出問(wèn)題詳解的文章就介紹到這了,更多相關(guān)C/C++溢出問(wèn)題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++ vector類(lèi)的模擬實(shí)現(xiàn)方法
這篇文章主要介紹了C++ vector類(lèi)的模擬實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05利用C語(yǔ)言來(lái)求最大連續(xù)子序列乘積的方法
這篇文章主要介紹了利用C語(yǔ)言來(lái)求最大連續(xù)子序列乘積的方法,基本的思路以外文中還附有相關(guān)ACM題目,需要的朋友可以參考下2015-08-08C++讀取訪問(wèn)權(quán)限沖突引發(fā)異常問(wèn)題的原因分析
C語(yǔ)言是一門(mén)通用計(jì)算機(jī)編程語(yǔ)言,廣泛應(yīng)用于底層開(kāi)發(fā),最近在用C++寫(xiě)代碼時(shí)經(jīng)常會(huì)遇到“引發(fā)了異常: 讀取訪問(wèn)權(quán)限沖突,所以這篇文章主要給大家介紹了關(guān)于C++讀取訪問(wèn)權(quán)限沖突引發(fā)異常問(wèn)題的相關(guān)資料,需要的朋友可以參考下2021-07-07c++統(tǒng)計(jì)文件中字符個(gè)數(shù)代碼匯總
本文給大家匯總介紹了3種使用C++實(shí)現(xiàn)統(tǒng)計(jì)文件中的字符個(gè)數(shù)的方法,非常的簡(jiǎn)單實(shí)用,有需要的小伙伴可以參考下。2015-09-09簡(jiǎn)述C語(yǔ)言中system()函數(shù)與vfork()函數(shù)的使用方法
這篇文章主要介紹了簡(jiǎn)述C語(yǔ)言中system()函數(shù)與vfork()函數(shù)的使用方法,是C語(yǔ)言入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-08-08海量數(shù)據(jù)處理系列之:用C++實(shí)現(xiàn)Bitmap算法
本篇文章是對(duì)用C++實(shí)現(xiàn)Bitmap算法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05