深信服面試記錄

1. 什麼是哈希衝突?解決哈希衝突的常用方法有哪些?

  • 由於哈希算法被計算的數據是無限的,而計算後的結果範圍有限,因此總會存在不同的數據經過計算後得到的值相同,這就是哈希衝突。
  • ①:開放地址法(再散列法):發生哈希衝突後,按照某一次序找到下一個空閒的單元,把衝突的元素放入。
  • ②:鏈地址法(拉鍊法):將哈希值相同的元素構成一個鏈表,head 放在散列表中。一般鏈表長度超過了 8 就轉爲紅黑樹,長度少於 6 個就變爲鏈表。
  • ③:再哈希法:同時構造多個不同的哈希函數,Hi = RHi(key) i= 1,2,3 … k;
    H1 = RH1(key) 發生衝突時,再用 H2 = RH2(key) 進行計算,直到衝突不再產生,這種方法不易產生聚集,但是增加了計算時間。
  • ④:創建公共溢出區:把哈希表分爲公共表和溢出表,如果發生了溢出,溢出的數據全部放在溢出區。

2. 快速排序的時間複雜度?如何防止其退化爲冒泡排序?

  • 快速排序最優的情況下時間複雜度爲:O( nlogn ),最差情況下時間複雜度:O( n^2 )
  • 快速排序的基準元素的選取非常重要,如果基準元素選取不當,可能影響排序過程的時間複雜度和空間複雜度。爲了避免快速排序退化爲冒泡排序以及遞歸棧過深等問題,通常依照“三者取中”的法則來選取基準元素。三者取中法是指在當前待排序的子序列中,將其首元素、尾元素和中間元素進行比較,在三者中取中值作爲本趟排序的基準元素。
    3. 常見段錯誤的原因以及排錯方法?
  • Linux 常見的 Segmentation Fault段錯誤是指訪問的內存超出了系統給這個程序所設定的內存空間,例如訪問了不存在的內存地址、訪問了系統保護的內存地址、訪問了只讀的內存地址等等情況。
  • ①:訪問了不存在的地址:
    int *p = NULL;
    *p = 0;
    
  • ②:訪問系統保護的內存地址:
     int *ptr = (int *)0;
     *ptr = 100;
    
  • ③:訪問只讀的內存地址:
    char *ptr = "test";
    strcpy(ptr, "TEST");
    
  • ④:棧溢出
    void main()
    {
        main();
    }
    
  • 段錯誤信息的獲取
  • dmesg 可以在應用程序 crash 掉時,顯示內核中保存的相關信息。如下所示,通過 dmesg 命令可以查看發生段錯誤的程序名稱、引起段錯誤發生的內存地址、指令指針地址、堆棧指針地址、錯誤代碼、錯誤原因等。
  • 段錯誤的調試方法
  • 使用 printf 輸出信息,使用 gccgdb

4. new 和 malloc 的區別?

  • new/deleteC++ 運算符關鍵字,需要編譯器支持。malloc/free 是庫函數,需要頭文件支持。
  • 使用 new 操作符申請內存分配時無須指定內存塊的大小,編譯器會根據類型信息自行計算。而 malloc 則需要顯式地指出所需內存的尺寸。
  • new 操作符內存分配成功時,返回的是對象類型的指針,類型嚴格與對象匹配,無須進行類型轉換,故 new 是符合類型安全性的操作符。而 malloc 內存分配成功則是返回 void * ,需要通過強制類型轉換將 void *指針轉換成我們需要的類型。
  • new 內存分配失敗時,會拋出 bac_alloc 異常。malloc 分配內存失敗時返回 NULL
  • new 操作符從自由存儲區(free store)上爲對象動態分配內存空間,而 malloc 函數從堆上動態分配內存。自由存儲區是 C++ 基於 new 操作符的一個抽象概念,凡是通過 new 操作符進行內存申請,該內存即爲自由存儲區。而堆是操作系統中的術語,是操作系統所維護的一塊特殊內存,用於程序的內存動態分配,C 語言使用 malloc 從堆上分配內存,使用 free 釋放已分配的對應內存。自由存儲區不等於堆,如上所述,佈局 new 就可以不位於堆中。
  • new 會先調用 operator new 函數,申請足夠的內存(通常底層使用 malloc 實現)。然後調用類型的構造函數,初始化成員變量,最後返回自定義類型指針。delete 先調用析構函數,然後調用 operator delete 函數釋放內存(通常底層使用 free 實現)。malloc/free 是庫函數,只能動態的申請和釋放內存,無法強制要求其做自定義類型對象構造和析構工作。
  • new 申請的內存通過 delete 釋放;malloc 申請的內存通過 free 釋放,new [ ] 申請的內存通過 delete [ ] 釋放。

5. C 與 C++ 的混合編程?

  • 需要用 extern "C" 來強制編譯器不要修改你的函數名。即指示編譯器這部分函數按照 C 語言的標準來編譯,而不是按照 C++ 的來。由於 CPP 支持多態性,也就是具有相同函數名的函數可以完成不同的功能,CPP 通常是通過參數區分具體調用的是哪一個函數。在編譯的時候,CPP 編譯器會將參數類型和函數名連接在一起,於是在程序編譯成爲目標文件以後, CPP 編譯器可以直接根據目標文件中的符號名將多個目標文件連接成一個目標文件或者可執行文件。但是在 C 語言中,由於完全沒有多態性的概念,C 編譯器在編譯時除了會在函數名前面添加一個下劃線之外,什麼也不會做(至少很多編譯器都是這樣乾的)。

6. C++ 的多態

  • 多態就是不同對象對同一行爲會有不同的狀態,例如長方形類和圓形類同一個求面積函數名字,但計算方式不同;
  • 多態分爲:靜態多態(編譯時的多態);動態多態(運行時的多態);
  • 函數重載就是一種靜態多態:函數重載的規則:
  • ①:函數名稱必須相同。
  • ②:參數列表必須不同(個數不同或類型不同或參數排列順序不同)。
  • ③:函數的返回類型可以相同也可以不相同。
  • ④:僅僅返回類型不同不足以成爲函數的重載。
  • C 語言只是在函數名前添加了下劃線,因此當工程中存在相同函數名的函數,就會產生衝突; C++支持多態是因爲,C++ 的編譯器在底層實際使用的不是函數的名字,而是被重新修飾過的一個較複雜的名字,被重新修飾後的名字包含了:函數的名字以及參數類型。這就是爲什麼函數重載中幾個同名函數要求其參數列表不同的原因。只要參數列表不同,編譯器在編譯時通過對函數名字進行重新修飾,將參數類型包含在最終的名字中,就可保證名字在底層的全局唯一性。
  • C++ 實現多態有兩個條件: 一是虛函數重寫,重寫就是用來設置不同狀態的;二是對象調用虛函數時必須是指針或者引用

7. 構造函數可以重載嗎?
史立坤的回答

  • 從 C++ 之父 Bjarne 的回答我們應該知道 C++ 爲什麼不支持構造函數是虛函數了,簡單講就是沒有意義。虛函數的作用在於通過子類的指針或引用來調用父類的那個成員函數。而構造函數是在創建對象時自己主動調用的,不可能通過子類的指針或者引用去調用。
  • 網絡上還有一個很普遍的解釋是這樣的:虛函數相應一個指向vtable虛函數表的指針,但是這個指向vtable的指針事實上是存儲在對象的內存空間的。假設構造函數是虛的,就須要通過 vtable來調用,但是對象還沒有實例化,也就是內存空間還沒有,怎麼找vtable呢?所以構造函數不能是虛函數。
  • 本人對這個觀點並不認同,這主要是因爲用什麼方式實現虛函數是編譯器的事情,使用Vtable只是大多數編譯器採用的一種手段,並不代表編譯器實現不了虛構造函數,編譯器之所以不支持虛構造函數主要原因就是沒有必要,所以正好這種實現方式也不支持,巧合而已。

8. 描述 TCP 連接的各個狀態過程
引用dreamispossible的原文鏈接:我覺得寫得不錯,略加修改改成符合自己思維的版本:

  • 剛開始客戶端處於 closed 的狀態,服務端處於 listen 狀態。然後:
  • 第一次握手:客戶端給服務端發一個 SYN 報文(即同步序列號報文),並指明客戶端的初始化序列號 ISN© 。此時客戶端處於 SYN_Send 狀態。
  • 第二次握手:服務器收到客戶端的 SYN 報文之後,會以自己的 SYN 報文作爲應答,並且也是指定了自己的初始化序列號 ISN(s),同時會把客戶端的 ISN + 1 作爲 ACK 的值,表示自己已經收到了客戶端的 SYN 報文,此時服務器處於 SYN_REVD 的狀態。
  • 第三次握手:客戶端收到 SYN 報文之後,會發送一個 ACK 報文,當然,也是一樣把服務器的 ISN + 1 作爲 ACK 的值,表示已經收到了服務端的 SYN 報文,此時客戶端處於 establised 狀態。
  • 服務器收到 ACK 報文之後,也處於 establised 狀態,此時,雙方以建立起了鏈接。
  • 在 socket 編程中,客戶端執行 connect() 時,將觸發三次握手。
  • hellomyshadow原文鏈接
  • 服務器第一次收到客戶端的 SYN 之後,就會處於 SYN_RCVD 狀態,此時雙方還沒有完全建立起連接,服務器會把此種狀態下的請求連接放入一個隊列中,此隊列稱之爲半連接隊列。
  • 與之對應,全連接隊列,就是已經完成了三次握手,建立起的連接被放入的隊列。如果隊列滿了,就可能會出現丟包現象。
  • 服務器發送完 SYN_ACK 後,如果未收到客戶端確認包,服務端進行首次重傳;等待一段時間仍未收到確認包,則進行第二次重傳;如果重傳次數超過系統規定的最大重傳次數,系統將該連接信息從半連接隊列中刪除。
    注意:每次重傳所等待的時間不一定相同,一般會是指數增長,如1s、2s、4s、8s …
  • TCP 連接的拆除需要發送四個包,因此稱爲四次揮手,客戶端或服務器均可主動發起揮手動作。
  • 剛開始雙方都處於 ESTABLISHED 狀態,假如是客戶端先發起關閉請求,四次揮手的過程如下:
  • 第一次揮手:客戶端發送一個 FIN 報文,報文中會指定一個序列號。此時客戶端處於 FIN_WAIT1 狀態;
  • 即發出連接釋放報文段(FIN=1,序號seq=u),並停止再發送數據,主動關閉 TCP 連接,進入 FIN_WAIT1 (終止等待1)狀態,等待服務端的確認。
  • 第二次揮手:服務端收到FIN包之後,會發送ACK報文,且把客戶端的序列號值+1 作爲ACK報文序列號值,表明已經收到客戶端的報文了,此時服務端處於CLOSE_WAIT狀態;
  • 即服務端收到連接釋放的報文段後,立即發出確認報文段(ACK=1,確認號ack=u+1,序號seq=v),服務端進入 CLOSE_WAIT(關閉等待)狀態,此時的 TCP 處於半關閉狀態,客戶端到服務端的連接釋放。客戶端收到服務端的確認後,進入 FIN_WAIT2(終止等待2)狀態,等待服務端發出的連接釋放報文段;
  • 第三次揮手:如果服務端也想斷開連接,和客戶端的第一次揮手一樣,發送 FIN 報文,且指定一個序列號。此時服務端處於 LAST_ACK 狀態。
  • 即服務端沒有要向客戶端發送的數據,服務端發出連接釋放的報文段(FIN=1,ACK=1,序號seq=w,確認號ack=u+1),服務端進入 LAST_ACK(最後確認)狀態,等待客戶端確認。
  • 第四次揮手:客戶端收到 FIN 之後,一樣發送一個 ACK 報文作爲應答,且把服務端的序列號值+1 作爲自己的 ACK 報文的序列號值,此時客戶端處於 TIME_WAIT 狀態,需要過一陣子纔會進入 CLOSED 狀態,爲了確保服務端收到自己的 ACK 報文。服務端收到 ACK 報文之後,就會關閉連接,也進入 CLOSED 狀態。
  • 即客戶端收到服務端的連接釋放報文段後,對此發出確認報文段(ACK=1,seq=u+1,ack=w+1),客戶端進入 TIME_WAIT(時間等待)狀態。此時 TCP 未釋放掉,需要經過時間等待計數器設置的時間 2MSL(報文最大生存時間)後,客戶端纔會進入 CLOSED 狀態。
  • 在 socket 編程中,任何一方執行 close() 操作即可產生回收操作。

9. 高併發?忘了

  • 不會 23333

10. socket 編程的函數

  • 只說了一個 connect( ) 函數…
  • 還想說一個 close( ) 函數的但是說完 connect( ) 函數就問了我一個我不會也沒有記住的問題了…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章