C語言為什么不檢查數組下標
引言
最近在查一個bug,查到最后發現是數組越界導致的。數組只有30個字節,代碼卻向這個數組填充了35個數據,這個bug還是偶現的,查到它確實廢了一番功夫。我就突然想到:C語言為什么不檢查數組下標呢???先來個demo驗證下。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int data[5]={0};
for(int i=0;i<8;++i)
{
printf("%d ",data[i]);
}
printf("\n");
return 0;
}
結果顯示,C語言還真的不檢查數組的下標。不僅沒有報錯,而且運行正常。
思考
這就讓我陷入了思考,C語言為什么不檢查下標呢?想上文這么簡單的,data數據組就5個數據,編譯器是知道的,為什么是訪問第8個數據時,編譯器來個報錯也沒有呢?我想到了之前的文章《??指針與數組??》中有如下示例代碼:
void main()
{
int data[4] = {0, 1, 2, 3};
int *p;
p = data +2;
printf("p[-1] is %d\n",p[-1]);
printf("*(p-1) is %d\n",*(p-1));
}
運行結果如下:
不僅可以編譯通過,還能正確的輸出結果為1。這表明,C的下標引用和間接訪問表達式是一樣的。
這讓我突然意識到,數組的這些特性,如數組名本質上是一個常量指針(不懂的同學看之前的推文《??指針與數組??》)C語言很難檢查下標合法性的。
如果C語言檢查數組是否越界,因為當數組出現在表達式中的時候,它會立刻被解讀成指針。此外,使用其他的指針變量也可以指向數組的任意元素,并且這個指針可以隨意進行加減運算。引用數組元素的時候,雖然你可以寫成a[i],但是它只不過是*(a+i)的一種表達,C語言本身的語法是無法檢查的,只能通過編譯器檢查。
那么編譯器將加入額外的代碼用于檢測數組是否越界,C的下標檢查所涉及的開銷比你開始想象的要多。編譯器必須在程序中插入指令,證實下標的結果所引用的元素和指針表達式所指向的元素屬于同一個數組,可能僅僅是個小功能,生成的程序的數組檢查占有大量的代碼空間,這必將影響程序的運行效率。
這也讓我意識到一個事情:數組的標識符(也就是數組名),它只包含并沒有包含數組的長度的信息,它只是個地址信息,也就是上面說的數組名本質上是個常量指針。讀到這里,請你想一下,C語言有提供數組長度的底層函數嗎???
答案是否定的,一般情況下,我們獲取一個數組的長度,我們可以獲取數組所占的內存大小,然后除以單個元素的內存大小計算數組長度。
int a[8];
printf("%d",sizeof(a)/sizeof(a[0]));
為什么不修復“漏洞”
既然我們發現了上述問題,那么那些C語言的大神為什么不修復這個“漏洞”呢?其他編程語言會吸取“教訓”嗎?學過JAVA的同學可以看下面代碼:
int [][] array = {{1,2,3},{1,4}};
System.out.println(array[1][2]);
這也是一個數組越界訪問的例子,但是JAVA的控制臺會打印如下信息:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2
at demo.Array.main(Array.java:31)。
會明確告訴你數組下標越界了,是的,高級語言JAVA是支持的。
那么我們就來講講C語言的設計目標:提供一種能以簡易的方式編譯、處理低級存儲器、僅產生少量的機器碼以及不需要任何運行環境支持便能運行的編程語言。
如果C語言加入了類似下標檢查,實現一個簡單的數組數據寫入,需要大量指令檢查下標是否正確,那么還符合C語言設計目標嗎?如果C語言有大量的這樣設計,操作系統內核還會使用C語言編寫嗎?單片機等實時系統還會使用C語言嗎?
所以C語言給了程序員更大空間,C語言執行效率高,可以直接訪問硬件,具有非常好的可移植性,所以世界上絕大部分的操作系統內核都是用C語言編寫的。
那么問題來了,JAVA都檢查了數組下標,C語言難道一點進步也沒有嗎?其實也不然,微軟在這一方面也做了貢獻。
在早期的CRT函數中也不對字符串指針或數組進行越界檢查,都是要求程序員確保空間足夠,因此也才也才有了在VS2005之后微軟提供的安全的CRT函數版本。(CRT函數不是本文的重點,不懂的同學請面向百度編程)。
總結
C語言為什么不檢查數組下標???答案一個字:快。