從一道面試題來(lái)看計(jì)算機(jī)專業(yè)基礎(chǔ)知識(shí)的重要性
以前碰到過(guò)這樣一道面試題:
// 請(qǐng)分析如下代碼的執(zhí)行結(jié)果。int main(){????int?i?=?0;????int?arr[3]?=?{0};for(;i<=3;i++) {arr[i] = 0;printf("Hello, World! \n");}return 0;}
我心想,頭一次面試碰到這么簡(jiǎn)單的面試題,面試官你是不是看不起我?
奮筆疾書(shū),我寫了個(gè)三行?Hello,World。
可是當(dāng)我自信滿滿的交給面試官的時(shí)候他卻詭異的笑了,仿佛在說(shuō),小伙計(jì),你太天真了,你是不是看不起我出的題?
這一笑笑得我是冷汗直冒,就跟偷偷打飛機(jī)被人發(fā)現(xiàn)了一樣,異常尷尬。
當(dāng)然,以上都是我胡謅出來(lái)的,一個(gè)面試不可能有這么多內(nèi)心戲,我是一個(gè)寫代碼的程序員,又不是戲精,哪來(lái)那么多想法。
但是,雖然內(nèi)心戲是胡謅的,可是這道面試題目確實(shí)實(shí)打?qū)嵉摹?/p>
接下來(lái)我們仔仔細(xì)細(xì)分析下這道題目,為了先快速得到執(zhí)行結(jié)果,我們先運(yùn)行一遍看看:
執(zhí)行結(jié)果可以看到,這段代碼竟然無(wú)限循環(huán)了,是不是很難理解。
想分析這段代碼,我們首先需要了解數(shù)組這個(gè)數(shù)據(jù)結(jié)構(gòu),簡(jiǎn)單來(lái)說(shuō):數(shù)組是用一組連續(xù)的內(nèi)存空間,存儲(chǔ)一組具有相同類型的數(shù)據(jù)的線性數(shù)據(jù)結(jié)構(gòu)。
數(shù)據(jù)邏輯結(jié)構(gòu)根據(jù)數(shù)組的邏輯結(jié)構(gòu)圖我們可以總結(jié)出一位數(shù)組的尋址公式為:arr[i]_address = base_address + i*data_size,其中?data_size?表示每個(gè)數(shù)據(jù)的磁盤空間大小。
根據(jù)這個(gè)尋址公式,我們來(lái)對(duì)上邊的代碼做出尋址行為的分析,當(dāng) i = 3 時(shí),理論上尋址公式為?arr[3]_address = arr[2]_address + i*data_size,此處需要注意的是因?yàn)?C 語(yǔ)言中,數(shù)組越界是一種未決行為,如果你是從事 Java 或者其他高級(jí)語(yǔ)言的話會(huì)發(fā)現(xiàn)對(duì)于數(shù)組越界是當(dāng)做一種異常行為來(lái)處理的,但是 C 語(yǔ)言不是。對(duì)于 C 語(yǔ)言來(lái)說(shuō),只要不是訪問(wèn)受限的內(nèi)存空間,所有的內(nèi)存空間都是可以自由訪問(wèn)的,哪么根據(jù)既定尋址公式,arr[3]會(huì)被定位到某塊本不屬于數(shù)組的內(nèi)存地址上,而這塊地址恰恰存儲(chǔ)的是我們定義的變量 i,也就是說(shuō)此時(shí)?arr[3] = 0?,相當(dāng)于 i = 0 時(shí)的情況,這樣就會(huì)導(dǎo)致無(wú)限循環(huán)。
如果i<=3改成i<3,結(jié)果才是我們想要的樣子。
我們想要的結(jié)果到此,程序的執(zhí)行結(jié)果我們分析完了,但是你可能對(duì)于上邊加粗的?而這塊地址恰恰存儲(chǔ)的是我們定義的變量 i?這句話有疑問(wèn),怎么就?arr[3] = 0?了呢?
這個(gè)時(shí)候我們大學(xué)學(xué)過(guò)的操作系統(tǒng)和計(jì)算機(jī)體系結(jié)構(gòu)以及甚至可能是編譯原理的知識(shí)就要排上用場(chǎng)啦。
我們都知道在寫一個(gè)函數(shù)時(shí)會(huì)使用形參,形參實(shí)例化時(shí)會(huì)形成一份拷貝,調(diào)用這個(gè)函數(shù)時(shí)會(huì)把實(shí)參傳進(jìn)去,調(diào)用完之后那些臨時(shí)拷貝又被釋放,那么計(jì)算機(jī)在調(diào)用函數(shù)時(shí)是如何進(jìn)行形參的保存和釋放的呢?這個(gè)時(shí)候會(huì)用到一個(gè)叫棧的數(shù)據(jù)結(jié)構(gòu)。
棧用于維護(hù)函數(shù)調(diào)用的上下文,離開(kāi)了棧,函數(shù)調(diào)用就無(wú)法實(shí)現(xiàn)。棧是從高地址向低地址延伸的。每個(gè)函數(shù)的每次調(diào)用都有它自己獨(dú)立的一個(gè)棧幀,這個(gè)棧幀中有它所需要的各種信息。
回到上邊那段代碼,產(chǎn)生死循環(huán)的第一個(gè)原因就是因?yàn)楹瘮?shù)調(diào)用棧的特殊性:函數(shù)體內(nèi)的局部變量是存在棧上的,且是連續(xù)壓棧。在 Linux 進(jìn)程的內(nèi)存布局中,棧區(qū)在高地址空間,從高向低增長(zhǎng)。變量 i 和 arr 在相鄰地址,且 i 比 arr 的地址大,所以 arr 越界正好訪問(wèn)到i。當(dāng)然,前提是 i 和 arr 元素要同類型,否則那段代碼仍是未決行為。
還有另一個(gè)原因是因?yàn)榫幾g器分配內(nèi)存和字節(jié)對(duì)齊,我們定義 3 個(gè)元素的數(shù)組加上一個(gè)變量 i 。4 個(gè)整數(shù)剛好能滿足 8 字節(jié)對(duì)齊 所以 i 的地址恰好跟著?arr[2]?后面導(dǎo)致死循環(huán)。如果數(shù)組本身有 4 個(gè)元素 則這里不會(huì)出現(xiàn)死循環(huán)。因?yàn)榫幾g器 64 位操作系統(tǒng)下,默認(rèn)會(huì)進(jìn)行 8 字節(jié)對(duì)齊 變量 i 的地址就不緊跟著數(shù)組后面了。
通過(guò)今天這道看似簡(jiǎn)單實(shí)則還是比較復(fù)雜的題目,可以說(shuō)坑很多,涉及到的知識(shí)點(diǎn)也不少,但恰恰這些知識(shí)點(diǎn)是我們大學(xué)學(xué)過(guò)的一些計(jì)算機(jī)基礎(chǔ)知識(shí),沒(méi)有涉及任何框架,也沒(méi)有任何的新技術(shù),可很多人還是答不上來(lái)。
簡(jiǎn)言之,你別看有些人整天這框架那框架的玩的很嗨,但是它的計(jì)算機(jī)專業(yè)素養(yǎng)是遠(yuǎn)遠(yuǎn)不夠的,職業(yè)生涯前期可能不會(huì)有什么區(qū)別,但是長(zhǎng)遠(yuǎn)來(lái)看,掌握了基礎(chǔ)知識(shí)的人上限勢(shì)必要高一些。
從今天開(kāi)始,本公眾號(hào)也會(huì)更新一些計(jì)算機(jī)基礎(chǔ)知識(shí)的專欄,自打我停止更新這段時(shí)間,我也思考了很多,總覺(jué)得每天都很焦慮,玩著手機(jī)的時(shí)候焦慮沒(méi)有好好學(xué)習(xí),學(xué)習(xí)的時(shí)候又心心念著電視劇劇情走向,今天學(xué)這個(gè)框架明天學(xué)那個(gè)框架,到頭來(lái)發(fā)現(xiàn)不但啥都沒(méi)學(xué)會(huì),原來(lái)會(huì)的東西還給忘了。
5 月份開(kāi)始,我會(huì)陸陸續(xù)續(xù)更新算法和數(shù)據(jù)結(jié)構(gòu)、計(jì)算機(jī)網(wǎng)絡(luò)、操作系統(tǒng)原理以及設(shè)計(jì)模式等和編程語(yǔ)言不強(qiáng)相關(guān)的一些內(nèi)容,希望大家能喜歡。
?往期推薦?
?
IntelliJ IDEA 2020.1 穩(wěn)定版發(fā)布
如果你喜歡這篇文章,歡迎在看轉(zhuǎn)發(fā)
生活很美好,明天見(jiàn)
