來自公眾號:技術讓夢想更偉大
作者:李肖遙
所謂的數組越界,簡單地講就是指數組下標變量的取值超過了初始定義時的大小,導致對數組元素的訪問出現在數組的范圍之外,這類錯誤也是C語言程序中最常見的錯誤之一。
在C語言中,數組必須是靜態的。換而言之,數組的大小必須在程序運行前就確定下來。由于C語言并不具有類似Java等語言中現有的靜態分析工具的功能,可以對程序中數組下標取值范圍進行嚴格檢查,一旦發現數組上溢或下溢,都會因拋出異常而終止程序。也就是說,C語言并不檢驗數組邊界,數組的兩端都有可能越界,從而使其他變量的數據甚至程序代碼被破壞。
因此,數組下標的取值范圍只能預先推斷一個值來確定數組的維數,而檢驗數組的邊界是程序員的職責。
一般情況下,數組的越界錯誤主要包括兩種:數組下標取值越界與指向數組的指針的指向范圍越界。
數組下標取值越界數組下標取值越界主要是指訪問數組的時候,下標的取值不在已定義好的數組的取值范圍內,而訪問的是無法獲取的內存地址。例如,對于數組inta[3],它的下標取值范圍是[0,2](即a[0]、a[1]與a[2])。如果我們的取值不在這個范圍內(如a[3]),就會發生越界錯誤。示例代碼如下所示:
inta[3];
inti=0;
for(i=0;i<4;i++)
{
a[i]=i;
}
for(i=0;i<4;i++)
{
printf("a[%d]=%d ",i,a[i]);
}
很顯然,在上面的示例程序中,訪問a[3]是非法的,將會發生越界錯誤。因此,我們應該將上面的代碼修改成如下形式:
inta[3];
inti=0;
for(i=0;i<3;i++)
{
a[i]=i;
}
for(i=0;i<3;i++)
{
printf("a[%d]=%d ",i,a[i]);
}
指向數組的指針的指向范圍越界指向數組的指針的指向范圍越界是指定義數組時會返回一個指向第一個變量的頭指針,對這個指針進行加減運算可以向前或向后移動這個指針,進而訪問數組中所有的變量。但在移動指針時,如果不注意移動的次數和位置,會使指針指向數組以外的位置,導致數組發生越界錯誤。下面的示例代碼就是移動指針時沒有考慮到移動的次數和數組的范圍,從而使程序訪問了數組以外的存儲單元。
inti;
int*p;
inta[5];
/*數組a的頭指針賦值給指針p*/
p=a;
for(i=0;i<10;i++)
{
/*指針p指向的變量*/
*p=i+10;
/*指針p下一個變量*/
p++;
}
在上面的示例代碼中,for循環會使指針p向后移動10次,并且每次向指針指向的單元賦值。但是,這里數組a的下標取值范圍是[0,4](即a[0]、a[1]、a[2]、a[3]與a[4])。因此,后5次的操作會對未知的內存區域賦值,而這種向內存未知區域賦值的操作會使系統發生錯誤。正確的操作應該是指針移動的次數與數組中的變量個數相同,如下面的代碼所示:
inti;
int*p;
inta[5];
/*數組a的頭指針賦值給指針p*/
p=a;
for(i=0;i<5;i++)
{
/*指針p指向的變量*/
*p=i+10;
/*指針p下一個變量*/
p++;
}
為了加深大家對數組越界的了解,下面通過一段完整的數組越界示例來演示編程中數組越界將會導致哪些問題。
1#definePASSWORD"123456"
2intTest(char*str)
3{
4intflag;
5charbuffer[7];
6flag=strcmp(str,PASSWORD);
7strcpy(buffer,str);
8returnflag;
9}
10intmain(void)
11{
12intflag=0;
13charstr[1024];
14while(1)
15{
16printf("請輸入密碼:");
17scanf("%s",str);
18flag=Test(str);
19if(flag)
20{
21printf("密碼錯誤! ");
22}
23else
24{
25printf("密碼正確! ");
26}
27}
28return0;
29}
上面的示例代碼模擬了一個密碼驗證的例子,它將用戶輸入的密碼與宏定義中的密碼123456進行比較。很顯然,本示例中最大的設計漏洞就在于Test()函數中的strcpy(buffer,str)調用。
由于程序將用戶輸入的字符串原封不動地復制到Test()函數的數組charbuffer[7]中。因此,當用戶的輸入大于7個字符的緩沖區尺寸時,就會發生數組越界錯誤,這也就是大家所謂的緩沖區溢出Bufferoverflow漏洞。
但是要注意,如果這個時候我們根據緩沖區溢出發生的具體情況填充緩沖區,不但可以避免程序崩潰,還會影響到程序的執行流程,甚至會讓程序去執行緩沖區里的代碼。示例運行結果為:
1請輸入密碼:12345
2密碼錯誤!
3請輸入密碼:123456
4密碼正確!
5請輸入密碼:1234567
6密碼正確!
7請輸入密碼:aaaaaaa
8密碼正確!
9請輸入密碼:0123456
10密碼錯誤!
11請輸入密碼:
在示例代碼中,flag變量實際上是一個標志變量,其值將決定著程序是進入密碼錯誤的流程(非0)還是“密碼正確”的流程(0)。當我們輸入錯誤的字符串1234567或者aaaaaaa,程序也都會輸出“密碼正確”。但在輸入0123456的時候,程序卻輸出“密碼錯誤”,這究竟是為什么呢?
其實,原因很簡單。當調用Test()函數時,系統將會給它分配一片連續的內存空間,而變量charbuffer[7]與intflag將會緊挨著進行存儲,用戶輸入的字符串將會被復制進buffer[7]中。如果這個時候,我們輸入的字符串數量超過6個(注意,有字符串截斷符也算一個),那么超出的部分將破壞掉與它緊鄰著的flag變量的內容。
當輸入的密碼不是宏定義的123456時,字符串比較將返回1或-1。我們都知道,內存中的數據按照4字節(DWORD)逆序存儲,所以當flag為1時,在內存中存儲的是0x01000000。如果我們輸入包含7個字符的錯誤密碼,如aaaaaaa,那么字符串截斷符0x00將寫入flag變量,這樣溢出數組的一個字節0x00將恰好把逆序存放的flag變量改為0x00000000。在函數返回后,一旦main函數的flag為0,就會輸出“密碼正確”。這樣,我們就用錯誤的密碼得到了正確密碼的運行效果。
而對于0123456,因為在進行字符串的大小比較時,它小于123456,flag的值是-1,在內存中將按照補碼存放負數,所以實際存儲的不是0x01000000而是0xffffffff。那么字符串截斷后符0x00淹沒后,變成0x00ffffff,還是非0,所以沒有進入正確分支。
其實,本示例只是用一個字節淹沒了鄰接變量,導致程序進入密碼正確的處理流程,使設計的驗證功能失效。
盡量顯式地指定數組的邊界在C語言中,為了提高運行效率,給程序員更大的空間,為指針操作帶來更多的方便,C語言內部本身不檢查數組下標表達式的取值是否在合法范圍內,也不檢查指向數組元素的指針是不是移出了數組的合法區域。因此,在編程中使用數組時就必須格外謹慎,在對數組進行讀寫操作時都應當進行相應的檢查,以免對數組的操作超過數組的邊界,從而發生緩沖區溢出漏洞。
要避免程序因數組越界所發生的錯誤,首先就需要從數組的邊界定義開始。盡量顯式地指定數組的邊界,即使它已經由初始化值列表隱式指定。示例代碼如下所示:
inta[]={1,2,3,4,5,6,7,8,9,10};
很顯然,對于上面的數組a[],雖然編譯器可以根據始化值列表來計算出數組的長度。但是,如果我們顯式地指定該數組的長度,例如:
inta[10]={1,2,3,4,5,6,7,8,9,10};
它不僅使程序具有更好的可讀性,并且大多數編譯器在數組長度小于初始化值列表的長度時還會發生相應警告。
當然,也可以使用宏的形式來顯式指定數組的邊界(實際上,這也是最常用的指定***),如下面的代碼所示:
#defineMAX10
…
inta[MAX]={1,2,3,4,5,6,7,8,9,10};
除此之外,在C99標準中,還允許我們使用單個指示符為數組的兩段“分配”空間,如下面的代碼所示:
inta[MAX]={1,2,3,4,5,[MAX-5]=6,7,8,9,10};
在上面的a[MAX]數組中,如果MAX大于10,數組中間將用0值元素進行填充(填充的個數為MAX-10,并從a[5]開始進行0值填充);如果MAX小于10,[MAX-5]之前的5個元素(1,2,3,4,5)中將有幾個被[MAX-5]之后的5個元素(6,7,8,9,10)所覆蓋,示例代碼如下所示:
1#defineMAX10
2#defineMAX115
3#defineMAX26
4intmain(void)
5{
6inta[MAX]={1,2,3,4,5,[MAX-5]=6,7,8,9,10};
7intb[MAX1]={1,2,3,4,5,[MAX1-5]=6,7,8,9,10};
8intc[MAX2]={1,2,3,4,5,[MAX2-5]=6,7,8,9,10};
9inti=0;
10intj=0;
11intz=0;
12printf("a[MAX]: ");
13for(i=0;i<MAX;i++)
14{
15printf("a[%d]=%d",i,a[i]);
16}
17printf(" b[MAX1]: ");
18for(j=0;j<MAX1;j++)
19{
20printf("b[%d]=%d",j,b[j]);
21}
22printf(" c[MAX2]: ");
23for(z=0;z<MAX2;z++)
24{
25printf("c[%d]=%d",z,c[z]);
26}
27printf(" ");
28return0;
29}
運行結果為:
1a[MAX]:
2a[0]=1a[1]=2a[2]=3a[3]=4a[4]=5a[5]=6a[6]=7a[7]=8a[8]=9a[9]=10
3b[MAX1]:
4b[0]=1b[1]=2b[2]=3b[3]=4b[4]=5b[5]=0b[6]=0b[7]=0b[8]=0b[9]=0b[10]=6b[11]=7b[12]=8b[13]=9b[14]=10
5c[MAX2]:
6c[0]=1c[1]=6c[2]=7c[3]=8c[4]=9c[5]=10
對數組做越界檢查,確保索引值位于合法的范圍之內
要避免數組越界,除了上面所闡述的顯式指定數組的邊界之外,還可以在數組使用之前進行越界檢查,檢查數組的界限和字符串(也以數組的方式存放)的結束,以保證數組索引值位于合法的范圍之內。例如,在寫處理數組的函數時,一般應該有一個范圍參數;在處理字符串時總檢查是否遇到空字符‘ 主站蜘蛛池模板: 欧美91精品久久久久网免费 | 亚洲一区二区免费 | 久久99热精品免费观看欧美 | 美国三级 | 特级做a爰片毛片免费看 | 国产亚洲欧美ai在线看片 | 日本道综合一本久久久88 | 久久99久久99精品 | 久久免费小视频 | 亚洲一级免费视频 | 国产成人精品免费视频 | 亚洲一区日韩一区欧美一区a | 日韩欧美国产高清在线观看 | 美国特级毛片 | 国产精品久久久久三级 | 国产精品久久久久国产精品 | 男女视频免费网站 | 精品小视频在线观看 | 国内精品九一在线播放 | 免费在线成人网 | 国产一久久香蕉国产线看观看 | 99国产精品欧美久久久久久影院 | 国产高清在线精品一区a | 国产精品99久久久 | 欧美成人精品第一区 | 奇米888四色在线精品 | 国产a视频 | 中文字幕在线观看亚洲日韩 | 久久久不卡国产精品一区二区 | 亚洲欧美日韩在线精品一区二区 | 久久综合久久美利坚合众国 | 亚洲欧美久久一区二区 | 免费久久精品视频 | 欧美午夜视频一区二区 | 欧美性巨大欧美 | 性刺激久久久久久久久 | 手机在线视频一区 | 国产日韩视频在线观看 | 国产最猛性xxxxxx69交 | www久久com| 日韩欧美三级在线观看 |