|
引言: \& m8 g9 O9 a7 a' |, m& U
最近在查一個(gè)bug,查到最后發(fā)現(xiàn)是數(shù)組越界導(dǎo)致的。數(shù)組只有30個(gè)字節(jié),代碼卻向這個(gè)數(shù)組填充了35個(gè)數(shù)據(jù),這個(gè)bug還是偶現(xiàn)的,查到它確實(shí)廢了一番功夫。我就突然想到:C語言為什么不檢查數(shù)組下標(biāo)呢???先來個(gè)demo驗(yàn)證下8 A: q5 a& |0 X) V7 {' D
#include#include
7 j- [9 _9 D5 c& K7 X iint main(){ int data[5]={0}; for(int i=0;i8;++i) { printf("%d ",data); } printf("6 O- J; Z# L: z( O" H
");
* w+ A+ G: _/ Q' N4 k$ Q0 ^* K return 0;}結(jié)果顯示,C語言還真的不檢查數(shù)組的下標(biāo)。不僅沒有報(bào)錯(cuò),而且運(yùn)行正常9 } B" u; G' }2 ^! Z8 j% M7 t g' w# M
2qxtgd2phl164014188000.png (21.41 KB, 下載次數(shù): 0)
下載附件
保存到相冊
2qxtgd2phl164014188000.png
2024-9-1 11:48 上傳
" [! ]) J) Z" v$ `3 P思考
( p: j9 a8 Z9 \ D; A這就讓我陷入了思考,C語言為什么不檢查下標(biāo)呢?想上文這么簡單的,data數(shù)據(jù)組就5個(gè)數(shù)據(jù),編譯器是知道的,為什么是訪問第8個(gè)數(shù)據(jù)時(shí),編譯器來個(gè)報(bào)錯(cuò)也沒有呢?我想到了之前的文章《指針與數(shù)組》中有如下示例代碼:
- P, x, [0 o4 h9 \void main(){ int data[4] = {0, 1, 2, 3}; int *p; p = data +2; printf("p[-1] is %d: P# F1 l2 O( w6 J" b
",p[-1]); printf("*(p-1) is %d
; G# Y2 I; B6 D* V- N8 p' `",*(p-1));}運(yùn)行結(jié)果如下
( k% q u3 G& _: b2 Y1 J6 {7 U
x30lsqizprm64014188101.png (4.23 KB, 下載次數(shù): 0)
下載附件
保存到相冊
x30lsqizprm64014188101.png
2024-9-1 11:48 上傳
8 e1 P6 o1 y& v, [$ b
不僅可以編譯通過,還能正確的輸出結(jié)果為1。這表明,C的下標(biāo)引用和間接訪問表達(dá)式是一樣的。這讓我突然意識(shí)到,數(shù)組的這些特性,如數(shù)組名本質(zhì)上是一個(gè)常量指針(不懂的同學(xué)看之前的推文《指針與數(shù)組》)C語言很難檢查下標(biāo)合法性的。如果C語言檢查數(shù)組是否越界,因?yàn)楫?dāng)數(shù)組出現(xiàn)在表達(dá)式中的時(shí)候,它會(huì)立刻被解讀成指針。此外,使用其他的指針變量也可以指向數(shù)組的任意元素,并且這個(gè)指針可以隨意進(jìn)行加減運(yùn)算。引用數(shù)組元素的時(shí)候,雖然你可以寫成a,但是它只不過是*(a+i)的一種表達(dá),C語言本身的語法是無法檢查的,只能通過編譯器檢查。那么編譯器將加入額外的代碼用于檢測數(shù)組是否越界,C的下標(biāo)檢查所涉及的開銷比你開始想象的要多。編譯器必須在程序中插入指令,證實(shí)下標(biāo)的結(jié)果所引用的元素和指針表達(dá)式所指向的元素屬于同一個(gè)數(shù)組,可能僅僅是個(gè)小功能,生成的程序的數(shù)組檢查占有大量的代碼空間,這必將影響程序的運(yùn)行效率。這也讓我意識(shí)到一個(gè)事情:數(shù)組的標(biāo)識(shí)符(也就是數(shù)組名),它只包含并沒有包含數(shù)組的長度的信息,它只是個(gè)地址信息,也就是上面說的數(shù)組名本質(zhì)上是個(gè)常量指針。讀到這里,請你想一下,C語言有提供數(shù)組長度的底層函數(shù)嗎???答案是否定的,一般情況下,我們獲取一個(gè)數(shù)組的長度,我們可以獲取數(shù)組所占的內(nèi)存大小,然后除以單個(gè)元素的內(nèi)存大小計(jì)算數(shù)組長度。int a[8];printf("%d",sizeof(a)/sizeof(a[0]));8 s0 H% S8 j" z7 F
為什么不修復(fù)“漏洞” l: _ u. B" T/ c, i
既然我們發(fā)現(xiàn)了上述問題,那么那些C語言的大神為什么不修復(fù)這個(gè)“漏洞”呢?其他編程語言會(huì)吸取“教訓(xùn)”嗎?學(xué)過JAVA的同學(xué)可以看下面代碼' l9 V C) Y% ]- g
int [][] array = {{1,2,3},{1,4}};System.out.println(array[1][2]);這也是一個(gè)數(shù)組越界訪問的例子,但是JAVA的控制臺(tái)會(huì)打印如下信息Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2( l! b, ^) q, p
at demo.Array.main(Array.java:31)
' Z- l# y1 G( t8 @* d, l會(huì)明確告訴你數(shù)組下標(biāo)越界了,是的,高級(jí)語言JAVA是支持的。那么我們就來講講C語言的設(shè)計(jì)目標(biāo):提供一種能以簡易的方式編譯、處理低級(jí)存儲(chǔ)器、僅產(chǎn)生少量的機(jī)器碼以及不需要任何運(yùn)行環(huán)境支持便能運(yùn)行的編程語言。如果C語言加入了類似下標(biāo)檢查,實(shí)現(xiàn)一個(gè)簡單的數(shù)組數(shù)據(jù)寫入,需要大量指令檢查下標(biāo)是否正確,那么還符合C語言設(shè)計(jì)目標(biāo)嗎?如果C語言有大量的這樣設(shè)計(jì),操作系統(tǒng)內(nèi)核還會(huì)使用C語言編寫嗎?單片機(jī)等實(shí)時(shí)系統(tǒng)還會(huì)使用C語言嗎?所以C語言給了程序員更大空間,C語言執(zhí)行效率高,可以直接訪問硬件,具有非常好的可移植性,所以世界上絕大部分的操作系統(tǒng)內(nèi)核都是用C語言編寫的。那么問題來了,JAVA都檢查了數(shù)組下標(biāo),C語言難道一點(diǎn)進(jìn)步也沒有嗎?其實(shí)也不然,微軟在這一方面也做了貢獻(xiàn)。在早期的CRT函數(shù)中也不對字符串指針或數(shù)組進(jìn)行越界檢查,都是要求程序員確?臻g足夠,因此也才也才有了在VS2005之后微軟提供的安全的CRT函數(shù)版本。(CRT函數(shù)不是本文的重點(diǎn),不懂的同學(xué)請面向百度編程)。1 } c4 j: x7 e5 _: A
總結(jié)1 a' k' A7 v$ w
C語言為什么不檢查數(shù)組下標(biāo)???答案一個(gè)字:
/ _3 d6 C r6 q快
. s" B' W9 ^' A2 O' t/ JEND0 E% ` _' f7 M
bmxoi42hi0i64014188201.gif (170.56 KB, 下載次數(shù): 0)
下載附件
保存到相冊
bmxoi42hi0i64014188201.gif
2024-9-1 11:48 上傳
, _* h& Z9 O! g3 T( R! G* n% {
edn0ibagz5064014188301.gif (66.66 KB, 下載次數(shù): 0)
下載附件
保存到相冊
edn0ibagz5064014188301.gif
2024-9-1 11:48 上傳
: q, B3 W+ h% R& U& G4 x' M
?STM32 IIC詳解/ h' ]5 B. o; O. k
?VScode 調(diào)試C語言 必讀
3 _* D3 z7 b4 L5 E" X d?單片機(jī)中volatile的應(yīng)用
' h# E5 w: J/ ^: b7 R1 ?# R?聯(lián)合體在單片機(jī)編程中的應(yīng)用 必讀! o+ G) T4 e8 T
?STM32串口開發(fā)之環(huán)形緩沖區(qū) |
|