練一練!3道經(jīng)典嵌入式C 面試題,答案在文末

題一,堆和棧的區(qū)別是?
題二,Volatile與Register的區(qū)別是?
題三,ARM里的大端格式和小端格式分別是什么意思?
題一答案:
(1)存儲(chǔ)內(nèi)容不同
棧:在函數(shù)調(diào)用時(shí),棧中存放的是函數(shù)中(最底下是函數(shù)調(diào)用后的下一條指令)的各個(gè)參數(shù)(局部變量)。
堆:一般是在堆的頭部用一個(gè)字節(jié)存放堆的大小。堆中的具體內(nèi)容由程序員分配。
(2)管理方式上不同
棧:由系統(tǒng)自動(dòng)分配并釋放空間。 例如,聲明在函數(shù)中一個(gè)局部變量 int b; 系統(tǒng)自動(dòng)在棧中為b開(kāi)辟空間,當(dāng)對(duì)應(yīng)的生存周期結(jié)束后??臻g被自動(dòng)釋放。
堆:需要程序員指定大小手動(dòng)申請(qǐng)和手動(dòng)釋放,在C語(yǔ)言中使用malloc函數(shù)申請(qǐng),使用free函數(shù)釋放。
(3)空間大小不同
棧:獲取空間較小。在Windows下一般大小是1M或2M,當(dāng)剩余棧空間不足時(shí),分配失敗overflow。
堆:獲得空間根據(jù)系統(tǒng)的有效虛擬內(nèi)存有關(guān),比較靈活、大。
(4)能否產(chǎn)生碎片不同
棧:不會(huì)產(chǎn)生碎片,空間連續(xù)。
堆:采用的是鏈表的存儲(chǔ)方式,會(huì)產(chǎn)生碎片。
(5)生長(zhǎng)方向不同
棧: 向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存區(qū)域。
堆: 向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表空閑內(nèi)存地址來(lái)存儲(chǔ)的,自然不連續(xù),而鏈表的遍歷方向是由低地址向高地址。
(6)分配方式不同
棧:有2種分配方式:靜態(tài)分配和動(dòng)態(tài)分配,靜態(tài)由編譯器完成,例如局部變量;動(dòng)態(tài)由malloc函數(shù)實(shí)現(xiàn),由編譯器進(jìn)行釋放。
堆: 都是動(dòng)態(tài)分配的,沒(méi)有靜態(tài)分配的堆。
(7)分配效率不同
棧:由系統(tǒng)自動(dòng)分配,速度較快。但程序員無(wú)法控制。
堆:由new分配的內(nèi)存,一般速度比較慢,而且容易產(chǎn)生內(nèi)存碎片,不過(guò)用起來(lái)最方便。
題二答案:
a.volatile
volatile是易變的,不穩(wěn)定的意思,volatile是關(guān)鍵字,是一種類(lèi)型修飾符,用它修飾的變量表示可以被某些編譯器未知的因素更改,比如操作系統(tǒng)、硬件或者其他線程等,遇到這個(gè)關(guān)鍵字聲明的變量,編譯器對(duì)訪問(wèn)該變量的代碼不在進(jìn)行優(yōu)化,從而可以提供對(duì)特殊地址的穩(wěn)定訪問(wèn)。那么什么是編譯器優(yōu)化呢?
為了提高運(yùn)行效率,攻城師們費(fèi)盡心機(jī)地把代碼優(yōu)化,優(yōu)化程序運(yùn)行時(shí)存取速度。一般,分為硬件優(yōu)化和軟件優(yōu)化。硬件優(yōu)化,流水線工作,詳細(xì)可以參考《計(jì)算機(jī)組成原理》。軟件優(yōu)化,一部分是程序猿們做的代碼優(yōu)化(前提你得有優(yōu)化的思路和能力),還有一部分就是我們的編譯器優(yōu)化了。
現(xiàn)代的編譯器經(jīng)過(guò)那么多年的發(fā)展,已經(jīng)比較成熟,它會(huì)把多余的變量忽略掉,讓代碼的運(yùn)行效率更高。默認(rèn)情況下,編譯器都會(huì)對(duì)代碼進(jìn)行優(yōu)化,會(huì)把一些變量在寄存器里存取,而不是在內(nèi)存里存取,如此一來(lái),CPU在自己家里拿東西當(dāng)然比從內(nèi)存那里拿東西要快得多。舉個(gè)小栗子:
int i = 5;
int a = i;
……
int b = i;
編譯器發(fā)現(xiàn)兩次從i讀數(shù)據(jù)的代碼之間,并沒(méi)有對(duì)i進(jìn)行過(guò)操作,它會(huì)自動(dòng)把上次讀的數(shù)據(jù)放在b中,而不是重新從i里面讀取。
而volatile關(guān)鍵字告訴編譯器該變量是隨時(shí)可能發(fā)生變化的,每次使用它的時(shí)候必須從內(nèi)存中取出它的值,因而編譯器生成的匯編代碼會(huì)從原內(nèi)存地址中讀取數(shù)據(jù)使用,而不是從寄存器或者緩存中讀取,從而保證了對(duì)特殊地址的穩(wěn)定訪問(wèn)。
簡(jiǎn)言之,狀態(tài)要經(jīng)常變化的,為了防止我們編譯優(yōu)化而導(dǎo)致的存取的數(shù)據(jù)不同步的問(wèn)題,這時(shí)我們就需要用到volatile。那具體到什么場(chǎng)景下需要用到volatile關(guān)鍵字呢?
1、并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器);
2、一個(gè)中斷服務(wù)子程序中會(huì)訪問(wèn)到的非自動(dòng)變量();
3、多線程應(yīng)用中被幾個(gè)任務(wù)共享的變量;
上面提到了非自動(dòng)變量,這里進(jìn)一步對(duì)幾種變量做一番解釋?zhuān)?/p>
自動(dòng)變量:是在函數(shù)內(nèi)部定義和使用的變量,它是局部變量。
非自動(dòng)變量:有兩種,一種是全局變量,一種是靜態(tài)變量。
全局變量:在函數(shù)外面定義的變量,只能定義一次,不能有重復(fù)的定義,不然就會(huì)發(fā)生錯(cuò)誤,而其他的文件要想使用這個(gè)變量,需要extern來(lái)聲明這個(gè)變量(也可省略,因?yàn)槟J(rèn)就是extern),這個(gè)聲明叫做引用聲明。
若不想被其他文件訪問(wèn),則用static關(guān)鍵字聲明為靜態(tài)變量。靜態(tài)變量與自動(dòng)變量的本質(zhì)區(qū)別是,靜態(tài)變量并不像自動(dòng)變量那樣使用堆棧機(jī)制來(lái)使用內(nèi)存。
而是為靜態(tài)變量分配固定的內(nèi)存,在程序運(yùn)行的整個(gè)過(guò)程中,它都會(huì)被保持,而不會(huì)被銷(xiāo)毀。這就是說(shuō)靜態(tài)變量的持續(xù)性是程序運(yùn)行的整個(gè)周期。這有利于我們共享一些數(shù)據(jù)。
如果靜態(tài)變量在函數(shù)內(nèi)部定義,則它的作用域就是在這個(gè)函數(shù)內(nèi)部,僅在這個(gè)函數(shù)內(nèi)部使用它才有效,但是它不同于自動(dòng)變量,自動(dòng)變量離開(kāi)函數(shù)后就會(huì)被銷(xiāo)毀,而靜態(tài)變量不會(huì)被銷(xiāo)毀。他在函數(shù)的整個(gè)運(yùn)行周期內(nèi)都會(huì)存在。
b. register
這個(gè)關(guān)鍵字請(qǐng)求編譯器盡可能的將變量存在CPU內(nèi)部寄存器中,而不是通過(guò)內(nèi)存尋址訪問(wèn),以提高效率。注意是盡可能,不是絕對(duì)。你想想,一個(gè)CPU 的寄存器也就那么幾個(gè)或幾十個(gè),你要是定義了很多很多register 變量,它累死也可能不能全部把這些變量放入寄存器吧。
題三答案:
當(dāng)前的存儲(chǔ)器,多以byte為訪問(wèn)的最小單元,當(dāng)一個(gè)邏輯上的地址必須分割為物理上的若干單元時(shí)就存在了先放誰(shuí)后放誰(shuí)的問(wèn)題, 于是端(endian)的問(wèn)題應(yīng)運(yùn)而生了, 對(duì)于不同的存儲(chǔ)方法, 就有大端(big-endian)和小端(little- endian)兩個(gè)描述。
字節(jié)排序按分為大端和小端,概念如下
大端(big endian): 低地址存放高有效字節(jié)
小端(little endian): 低字節(jié)存放低有效字節(jié)
現(xiàn)在主流的CPU, intel系列的是采用的little endian的格式存放數(shù)據(jù),而motorola系列的CPU采用的是big endian,ARM則同時(shí)支持 big和little。
舉個(gè)例子
int a = 0x12345678;
a是四字節(jié)的int類(lèi)型變量,需要占四個(gè)字節(jié)空間,假設(shè)變量a的首地址是0x2000,那么數(shù)據(jù)存儲(chǔ)在地址中的格式如下:
