面試總結-語言篇(從內存底層看待c/c++)

原創時間 2012年05月06日 22:29:06

根據自己面試和網上綜合整理

C語言程序的內存布局:

1. 代碼段 code or text

2. 只讀數據段 ro data

3. 已初始化讀寫數據段 rw data

4. 未初始化讀寫數據段 bss

5. 堆 heap

6. 棧 stack

但有時候也這麼說

1. 棧

2. 堆

3. 全局區或靜態區

4. 字元常量區

5. 程序代碼區

針對c和c++經常提到的問題,做一些簡單的總結,主要是從內存的角度來解釋:

1、Static關鍵字都有哪些用法?C和C++中有什麼區別?

2、虛基類、虛函數、純虛函數的用法及其區別?

3、C和C++如何動態申請內存和釋放內存?有什麼區別?

4、C語言編譯的過程?

上次騰訊面試還問到個題目,主要是因為我說c因為其結構化特點是萬能的,可以實現出任何我們想要的東西。然後主考官問我:

web頁面用c語言怎麼實現?

首先,介紹下c語言的編譯執行過程,比方說一個main.c的文件執行過程應當如下:

cpp(c預處理器)將mian.c處理為main.i

cc1(c編譯器)將main.i編譯為main.s

as(彙編器)將main.s彙編為main.o

ld(鏈接器)將main.o鏈接為可執行文件

最後執行即可。而我們平時所使用的gcc和g++往往將這幾個過程省略了,導致我們對其具體過程不是很清楚,但是可以通過加選項把一個個中間過程展現出來。

那麼c語言在具體執行的過程中到底幹了些什麼?具體是怎麼樣執行的呢?

C語言的代碼段顧名思義其實就是程序的代碼,以main函數為入口,我們可以通過彙編代碼來窺探其具體執行。電腦只是很簡單的執行代碼段的一條一條的指令而已。

我們已經知道,全局變數以及靜態變數等都已經存放於字元常量區以及全局區和靜態區里了。那麼在具體執行的過程中局部變數怎麼辦?函數那麼多,全部載入執行?

這個時候就要用到棧,通過棧幀結構來執行一個一個的函數。

沿著箭頭方向,地址增大。棧增長方向是向下增長的。

比方說在main函數(調用者)里調用了一個swap函數(被調用者),首先在main裡面一定有swap(x,y)這樣的結構。其中參數x和y就被當做參數壓入棧中。然後是返回地址,其實就是swap(x,y)後面的下一條指令地址,讓程序返回後能接著執行。而真知則為main的幀指針內容,保證正確執行。這樣在取參數的時候就能通過幀指針+4的倍數取到傳遞過來的參數了。在幀指針下面一般開始存儲本地一些臨時變數什麼的。棧指針指向最低處。

這樣代碼段的指令一條條執行,遇到函數則新建一個棧幀,執行完返回後將該棧幀清除掉即可。這樣不管多少個函數,棧都是不斷增長了又減小了這樣的變化著。多少個棧幀結構也是取決於函數調用嵌套的深度而已。因此我們在執行遞歸調用的時候可想而知,深度太深那得要多少個棧幀結構啊,效率啊!!!

現在回過頭來我們想想static的用法。

1. 局部靜態變數

2. 外部靜態變數(全局)/函數 聲明一個函數只能在本文件內使用,而非其他

3. 靜態數據成員/成員函數

其中前兩條是c的用法,c++是在前兩條的基礎上增加了第三條

為什麼呢?

因為static關鍵字生命的的都是放在全局區的。一般只初始化一次,以後都是不斷引用而已。

那麼c++第三條如何解釋呢?

因為一個成員被聲明為成員之後,就是靜態的。而c++的對象往往是是運行的時候才創建,哪裡創建呢?當然是在棧上。

調用形式一般為A a的話。那麼a實例的東西一般是在棧內,所以a.solve()調用形式是不合適的(其實可以),但並不能說明其內在本質。其初始化形式應該為A::solve,最好不要是a.solve()。

我們只需要知道,c中static是整個文件內大家的共享,而c++中類成員實現了一個類的所有成員實例的共享。初始化時一般如下:

<數據類型><類名>::<靜態數據成員名>=<值>

這表明:

(1) 初始化在類體外進行,而前面不加static,以免與一般靜態變數或對象相混淆。

  (2) 初始化時不加該成員的訪問許可權控制符private,public等。

  (3) 初始化時使用作用域運算符來標明它所屬類,因此,靜態數據成員是類的成員,而不是對象的成員。

第二個問題

虛基類:

當在多條繼承路徑上有一個公共的基類,在這些路徑中的某幾條匯合處,這個公共的基類就會產生多個實例(或多個副本),若只想保存這個基類的一個實例,可以將這個公共基類說明虛基類.

虛基類

虛函數:

虛函數的作用是實現動態聯編,也就是在程序的運行階段動態地選擇合適的成員函數,在定義了虛函數後,可以在基類的派生類中對虛函數重新定義,在派生類中重新定義的函數應與虛函數具有相同的形參個數和形參類型。以實現統一的介面,不同定義過程。如果在派生類中沒有對虛函數重新定義,則它繼承其基類的虛函數

1、 必須把動態聯編的行為定義為類的虛函數。  

2、 類之間存在子類型關係,一般表現為一個類從另一個類公有派生而來。  

3、 必須先使用基類指針指向子類型的對象,然後直接或者間接使用基類指針調用虛函數。

純虛函數

是一種特殊的虛函數,它的一般格式如下:  

class <類名>

{virtual <類型><函數名>(<參數表>)=0;…};

在許多情況下,在基類中不能對虛函數給出有意義的實現,而把它聲明為純虛函數,它的實現留給該基類的派生類去做。這就是純虛函數的作用。

下面從內存角度分析下virtual關鍵字在c++到底是如何工作的。

virtual 關鍵字用於修飾方法、屬性、索引器或事件聲明,並且允許在派生類中重寫這些對象。

對c++了解都應該知道虛函數(virtual function)是通過一張虛函數表(virtual table)來實現的,簡稱v-table。

假設有如下所示的一個繼承關係:

請注意,在這個繼承關係中,子類沒有重載任何父類的函數。那麼,在派生類的實例中,其虛函數表如下所示:

對於實例:Derive d;的虛函數表如下:

我們可以看到下面幾點:

1)虛函數按照其聲明順序放於表中。

2)父類的虛函數在子類的虛函數前面。

一般繼承(有虛函數覆蓋)

覆蓋父類的虛函數是很顯然的事情,不然,虛函數就變得毫無意義。下面,我們來看一下,如果子類中有虛函數重載了父類的虛函數,會是一個什麼樣子?假設,我們有下面這樣的一個繼承關係。

為了讓大家看到被繼承過後的效果,在這個類的設計中,我只覆蓋了父類的一個函數:f()。那麼,對於派生類的實例,其虛函數表會是下面的一個樣子:

我們從表中可以看到下面幾點,

1)覆蓋的f()函數被放到了虛表中原來父類虛函數的位置。

2)沒有被覆蓋的函數依舊。

多重繼承(無虛函數覆蓋)

下面,再讓我們來看看多重繼承中的情況,假設有下面這樣一個類的繼承關係。注意:子類並沒有覆蓋父類的函數。

對於子類實例中的虛函數表,是下面這個樣子:

我們可以看到:

1) 每個父類都有自己的虛表。

2) 子類的成員函數被放到了第一個父類的表中。(所謂的第一個父類是按照聲明順序來判斷的)

而一般的虛基類、純虛函數應該都是通過編譯器來判斷執行器修改內容的。

回到第三個問題。

我們知道c和c++都是在堆(heap)這個結構內來申請內存、釋放內存的。

c使用malloc和free,c++則是new和delete。申請釋放都差不多,那麼它們之間到底是否有差別呢?

C 語言的malloc() 和free() 並不會調用析構函數和構造函數。C++的 new 和 delete 操作符 是 "類意識" ,並且當調用new的時候會調用類的構造函數和當delete 調用的時候會調用析構函數。

注意:混合用malloc 和delete或者混合用new 和free 是不正確的。C++的new和delete是C++用構造器分配內存,用析構函數清除使用過的內存。

new/delete 優點:

new/delete調用 constructor/destructor.Malloc/free 不會.

new 不需要類型強制轉換。.Malloc 要對放回的指針強制類型轉換.

new/delete操作符可以被重載, malloc/free 不會

new 並不會強制要求你計算所需要的內存 ( 不像malloc)

注意:

malloc: 用於申請一段新的地址

calloc: 用於申請N段新的地址

realloc: realloc是給一個已經分配了地址的指針重新分配空間

free: 通過指針釋放內存

c申請返回的是一個指針,而c++返回的是一個副本。

C++中

注意:

· 如果刪除操作符被應用在基類中,並且其析構函數並不是虛函數,這將會引起內存泄露,因為只有基類的內存被釋放掉。

· 基類的析構函數不是純虛函數,將不能被作為基類而實現。

· 類的析構函數可以不是virtual

總結如下:

1. C 語言的malloc() 和free() 並不會調用析構函數和構造函數。C++的 new 和 delete 操作符 是 "類意識" ,並且當調用new的時候會調用類的構造函數和當delete 調用的時候會調用析構函數。

2. c函數返回一個指針,c++返回一個副本

3. C++虛析構函數中應當注意的

4. C++自身的保護機制

第四個問題已經講過了。

而用c怎麼實現web頁面。我答得是必須把它做一個clicklistener的監聽器,就是一個對象。把web頁面的每一個詞條什麼的必須是有url,顯示名稱之類的,必須做成一個個對象。在點擊時發送url給監聽器,監聽器給瀏覽器,然後就可以登錄該網站了。

監考官說那c沒有面向對象的東西啊,我說struct也能辦到。

監考官回答:其實是可以的,但也是不好的。實現的時候還是要具體的語言來實現,否則成本太高。

其實這個只要有那種面向對象的思想就好了。只是為了突出c語言的結構化特點而已。就像搭積木一樣可以搭建出任何東西。

好了,到此為止把。

其他的慢慢總結啊,未完待續.......


推薦閱讀:

面試 | 如何在面試中巧妙地"偽裝"成極具團隊精神的社交達人?
{面試禮儀} 如何應對面試官的9大疑問
關於安全工程師崗位的面試
19道小米網運維工程師筆試真題,你能通關嗎?

TAG:C | 面试问题 | 编程语言 |