C++面試題目彙總(四)

C++後臺開發面試常見問題彙總

C和C++語言基礎

 

  1. extern聲明變量在在外部定義?
  2. extern修飾函數?
  3. extern C的作用?用法?

 

  1. static修飾局部變量?
  2. static全局變量?(限定變量在一個編譯單元內,一個編譯單元就是指一個cpp和它包含的頭文件,這個回答可以結合編譯需要經歷的幾個過程來答)
  3. static修飾普通函數?
  4. static修飾成員變量?
  5. static修飾成員函數?

 

  • volatile是幹啥的
  1. 訪問寄存器要比訪問內存要塊,因此CPU會優先訪問該數據在寄存器中的存儲結果,但是內存中的數據可能已經發生了改變,而寄存器中還保留着原來的結果。爲了避免這種情況的發生將該變量聲明爲volatile,告訴CPU每次都從內存去讀取數據。
  2. 一個參數可以即是const又是volatile的嗎?可以,一個例子是隻讀狀態寄存器,是volatile是因爲它可能被意想不到的被改變,是const告訴程序不應該試圖去修改他。

 

  • 說說const的作用,越多越好
  1. const修飾全局變量;
  2. const修飾局部變量;
  3. const修飾指針,const int *;
  4. const修飾指針指向的對象, int * const;
  5. const修飾引用做形參;
  6. const修飾成員變量,必須在構造函數列表中初始化;
  7. const修飾成員函數,說明該函數不應該修改非靜態成員,但是這並不是十分可靠的,指針所指的非成員對象值可能會被改變

 

  1. new分配內存按照數據類型進行分配,malloc分配內存按照大小分配
  2. new不僅分配一段內存,而且會調用構造函數,但是malloc則不會。new的實現原理?但是還需要注意的是,之前看到過一個題說int p = new int與int p = new int()的區別,因爲int屬於C++內置對象,不會默認初始化,必須顯示調用默認構造函數,但是對於自定義對象都會默認調用構造函數初始化。翻閱資料後,在C++11中兩者沒有區別了,自己測試的結構也都是爲0;
  3. new返回的是指定對象的指針,而malloc返回的是void*,因此malloc的返回值一般都需要進行類型轉化;
  4. new是一個操作符可以重載,malloc是一個庫函數
  5. new分配的內存要用delete銷燬,malloc要用free來銷燬;delete銷燬的時候會調用對象的析構函數,而free則不會;
  6. malloc分配的內存不夠的時候,可以用realloc擴容。擴容的原理?new沒用這樣操作;
  7. new如果分配失敗了會拋出bad_malloc的異常,而malloc失敗了會返回NULL。因此對於new,正確的姿勢是採用try...catch語法,而malloc則應該判斷指針的返回值。爲了兼容很多c程序員的習慣,C++也可以採用new nothrow的方法禁止拋出異常而返回NULL;
  8. new和new[]的區別,new[]一次分配所有內存,多次調用構造函數,分別搭配使用delete和delete[],同理,delete[]多次調用析構函數,銷燬數組中的每個對象。而malloc則只能sizeof(int) * n;
  9. 如果不夠可以繼續談new和malloc的實現,空閒鏈表,分配方法(首次適配原則,最佳適配原則,最差適配原則,快速適配原則)。delete和free的實現原理,free爲什麼直到銷燬多大的空間?

 

  1. C++多態的實現?
    多態分爲靜態多態和動態多態。靜態多態是通過重載和模板技術實現,在編譯的時候確定。動態多態通過虛函數和繼承關係來實現,執行動態綁定,在運行的時候確定。
    動態多態實現有幾個條件:
    (1) 虛函數;
    (2) 一個基類的指針或引用指向派生類的對象;
    基類指針在調用成員函數(虛函數)時,就會去查找該對象的虛函數表。虛函數表的地址在每個對象的首地址。查找該虛函數表中該函數的指針進行調用。
    每個對象中保存的只是一個虛函數表的指針,C++內部爲每一個類維持一個虛函數表,該類的對象的都指向這同一個虛函數表。
    虛函數表中爲什麼就能準確查找相應的函數指針呢?因爲在類設計的時候,虛函數表直接從基類也繼承過來,如果覆蓋了其中的某個虛函數,那麼虛函數表的指針就會被替換,因此可以根據指針準確找到該調用哪個函數。
  2. 虛函數的作用?
    1. 虛函數用於實現多態,這點大家都能答上來
    2. 但是虛函數在設計上還具有封裝和抽象的作用。比如抽象工廠模式。

 

    1. 動態綁定是如何實現的?
      第一個問題中基本回答了,主要都是結合虛函數表來答就行。

 

    1. 靜態多態和動態多態。靜態多態是指通過模板技術或者函數重載技術實現的多態,其在編譯器確定行爲。動態多態是指通過虛函數技術實現在運行期動態綁定的技術。

 

    1. 虛函數表
    2. 虛函數表是針對類的還是針對對象的?同一個類的兩個對象的虛函數表是怎麼維護的?
    3. 編譯器爲每一個類維護一個虛函數表,每個對象的首地址保存着該虛函數表的指針,同一個類的不同對象實際上指向同一張虛函數表。

 

  • 純虛函數如何定義,爲什麼對於存在虛函數的類中析構函數要定義成虛函數
    爲了實現多態進行動態綁定,將派生類對象指針綁定到基類指針上,對象銷燬時,如果析構函數沒有定義爲析構函數,則會調用基類的析構函數,顯然只能銷燬部分數據。如果要調用對象的析構函數,就需要將該對象的析構函數定義爲虛函數,銷燬時通過虛函數表找到對應的析構函數。//純虛函數定義 virtual ~myClass() = 0;

 

  • 析構函數能拋出異常嗎
    答案肯定是不能。 C++標準指明析構函數不能、也不應該拋出異常。C++異常處理模型最大的特點和優勢就是對C++中的面向對象提供了最強大的無縫支持。那麼如果對象在運行期間出現了異常,C++異常處理模型有責任清除那些由於出現異常所導致的已經失效了的對象(也即對象超出了它原來的作用域),並釋放對象原來所分配的資源, 這就是調用這些對象的析構函數來完成釋放資源的任務,所以從這個意義上說,析構函數已經變成了異常處理的一部分。

 

(1) 如果析構函數拋出異常,則異常點之後的程序不會執行,如果析構函數在異常點之後執行了某些必要的動作比如釋放某些資源,則這些動作不會執行,會造成諸如資源泄漏的問題。
(2) 通常異常發生時,c++的機制會調用已經構造對象的析構函數來釋放資源,此時若析構函數本身也拋出異常,則前一個異常尚未處理,又有新的異常,會造成程序崩潰的問題。

 

  • 構造函數和析構函數中調用虛函數嗎?

 

  • 指針和引用的區別
  1. 指針保存的是所指對象的地址,引用是所指對象的別名,指針需要通過解引用間接訪問,而引用是直接訪問;
  2. 指針可以改變地址,從而改變所指的對象,而引用必須從一而終;
  3. 引用在定義的時候必須初始化,而指針則不需要;
  4. 指針有指向常量的指針和指針常量,而引用沒有常量引用;
  5. 指針更靈活,用的好威力無比,用的不好處處是坑,而引用用起來則安全多了,但是比較死板。

 

  • 指針與數組千絲萬縷的聯繫
  1. 一個一維int數組的數組名實際上是一個int* const 類型;
  2. 一個二維int數組的數組名實際上是一個int (*const p)[n];
  3. 數組名做參數會退化爲指針,除了sizeof

 

  1. 構造函數中計數初始化爲1;
  2. 拷貝構造函數中計數值加1;
  3. 賦值運算符中,左邊的對象引用計數減一,右邊的對象引用計數加一;
  4. 析構函數中引用計數減一;
  5. 在賦值運算符和析構函數中,如果減一後爲0,則調用delete釋放對象。
  6. share_prt<T>與weak_ptr<T>的區別?
//share_ptr可能出現循環引用,從而導致內存泄露

class A

{

public:

share_ptr<B> p;

};



class B

{

public:

share_ptr<A> p;

}



int main()

{

while(true)

{

share_prt<A> pa(new A()); //pa的引用計數初始化爲1

share_prt<B> pb(new B()); //pb的引用計數初始化爲1

pa->p = pb; //pb的引用計數變爲2

pb->p = pa; //pa的引用計數變爲2

}

//假設pa先離開,引用計數減一變爲1,不爲0因此不會調用class A的析構函數,因此其成員p也不會被析構,pb的引用計數仍然爲2;

//同理pb離開的時候,引用計數也不能減到0

return 0;

}



/*

** weak_ptr是一種弱引用指針,其存在不會影響引用計數,從而解決循環引用的問題

*/

  1. const_cast用於將const變量轉爲非const
  2. static_cast用的最多,對於各種隱式轉換,非const轉const,void*轉指針等, static_cast能用於多態想上轉化,如果向下轉能成功但是不安全,結果未知;
  3. dynamic_cast用於動態類型轉換。只能用於含有虛函數的類,用於類層次間的向上和向下轉化。只能轉指針或引用。向下轉化時,如果是非法的對於指針返回NULL,對於引用拋異常。要深入瞭解內部轉換的原理。
  4. reinterpret_cast幾乎什麼都可以轉,比如將int轉指針,可能會出問題,儘量少用;
  5. 爲什麼不使用C的強制轉換?C的強制轉換表面上看起來功能強大什麼都能轉,但是轉化不夠明確,不能進行錯誤檢查,容易出錯。

 

  • 內存對齊的原則
  1. 從0位置開始存儲;
  2. 變量存儲的起始位置是該變量大小的整數倍;
  3. 結構體總的大小是其最大元素的整數倍,不足的後面要補齊;
  4. 結構體中包含結構體,從結構體中最大元素的整數倍開始存;
  5. 如果加入pragma pack(n) ,取n和變量自身大小較小的一個。

 

  • 內聯函數有什麼優點?內聯函數與宏定義的區別?
  1. 宏定義在預編譯的時候就會進行宏替換;
  2. 內聯函數在編譯階段,在調用內聯函數的地方進行替換,減少了函數的調用過程,但是使得編譯文件變大。因此,內聯函數適合簡單函數,對於複雜函數,即使定義了內聯編譯器可能也不會按照內聯的方式進行編譯。
  3. 內聯函數相比宏定義更安全,內聯函數可以檢查參數,而宏定義只是簡單的文本替換。因此推薦使用內聯函數,而不是宏定義。
  4. 使用宏定義函數要特別注意給所有單元都加上括號,#define MUL(a, b) a b,這很危險,正確寫法:#define MUL(a, b) ((a) (b))

 

  • C++內存管理
  1. C++內存分爲那幾塊?(堆區,棧區,常量區,靜態和全局區)
  2. 每塊存儲哪些變量?
  3. 學會遷移,可以說到malloc,從malloc說到操作系統的內存管理,說道內核態和用戶態,然後就什麼高端內存,slab層,夥伴算法,VMA可以巴拉巴拉了,接着可以遷移到fork()。

 

  • STL裏的內存池實現
    STL內存分配分爲一級分配器和二級分配器,一級分配器就是採用malloc分配內存,二級分配器採用內存池。
  • 二級分配器設計的非常巧妙,分別給8k,16k,..., 128k等比較小的內存片都維持一個空閒鏈表,每個鏈表的頭節點由一個數組來維護。需要分配內存時從合適大小的鏈表中取一塊下來。假設需要分配一塊10K的內存,那麼就找到最小的大於等於10k的塊,也就是16K,從16K的空閒鏈表裏取出一個用於分配。釋放該塊內存時,將內存節點歸還給鏈表。
    如果要分配的內存大於128K則直接調用一級分配器。
    爲了節省維持鏈表的開銷,採用了一個union結構體,分配器使用union裏的next指針來指向下一個節點,而用戶則使用union的空指針來表示該節點的地址。

 

  • STL裏set和map是基於什麼實現的。紅黑樹的特點?

 

    1. set和map都是基於紅黑樹實現的。
    2. 紅黑樹是一種平衡二叉查找樹,與AVL樹的區別是什麼?AVL樹是完全平衡的,紅黑樹基本上是平衡的。
    3. 爲什麼選用紅黑數呢?因爲紅黑數是平衡二叉樹,其插入和刪除的效率都是N(logN),與AVL相比紅黑數插入和刪除最多隻需要3次旋轉,而AVL樹爲了維持其完全平衡性,在壞的情況下要旋轉的次數太多。
      紅黑樹的定義:
      (1) 節點是紅色或者黑色;
      (2) 父節點是紅色的話,子節點就不能爲紅色;
      (3) 從根節點到每個頁子節點路徑上黑色節點的數量相同;
      (4) 根是黑色的,NULL節點被認爲是黑色的。

 

  • STL裏的其他數據結構和算法實現也要清楚
    這個問題,把STL源碼剖析好好看看,不僅面試不慌,自己對STL的使用也會上升一個層次。

 

  • 必須在構造函數初始化式裏進行初始化的數據成員有哪些
    (1) 常量成員,因爲常量只能初始化不能賦值,所以必須放在初始化列表裏面
    (2) 引用類型,引用必須在定義的時候初始化,並且不能重新賦值,所以也要寫在初始化列表裏面
    (3) 沒有默認構造函數的類類型,因爲使用初始化列表可以不必調用默認構造函數來初始化,而是直接調用拷貝構造函數初始化

 

  • 模板特化
    (1) 模板特化分爲全特化和偏特化,模板特化的目的就是對於某一種變量類型具有不同的實現,因此需要特化版本。例如,在STL裏迭代器爲了適應原生指針就將原生指針進行特化。

 

  • 定位內存泄露
    (1)在windows平臺下通過CRT中的庫函數進行檢測;
    (2)在可能泄漏的調用前後生成塊的快照,比較前後的狀態,定位泄漏的位置
    (3)Linux下通過工具valgrind檢測

 

 

  • 手寫strcpy
  • char strcpy(char dst, const char src)
    {
    assert(dst);
    assert(src);
    char ret = dst;
    while((dst++ = src++) != '\0');
    return ret;
    }
    //該函數是沒有考慮重疊的
    char strcpy(char dst, const char src)
    {
    assert((dst != NULL) && (src != NULL));
    char ret = dst;
    int size = strlen(src) + 1;
    if(dst > src || dst < src + len)
    {
    dst = dst + size - 1;
    src = src + size - 1;
    while(size--)
    {
    dst-- = src--;
    }
    }
    else
    {
    while(size--)
    {
    dst++ = src++;
    }
    }
    return ret;
    }
    /*手寫memcpy函數*/
    
    void memcpy(void dst, const void src, size_t size)
    {
    if(dst == NULL || src == NULL)
    {
    return NULL;
    }
    void res = dst;
    char pdst = (char)dst;
    char psrc = (char)src;
    
    
    
    if(pdst > psrc && pdst < psrc + size) //重疊
    
    {
    
    pdst = pdst + size - 1;
    
    psrc = pdst + size - 1;
    
    while(size--)
    
    {
    
    *pdst-- = *psrc--;
    
    }
    
    }
    
    else //無重疊
    
    {
    
    while(size--)
    
    {
    
    *dst++ = *src++;
    
    }
    
    }
    
    return ret;
    
    }
    
    
    /*手寫strcat函數*/
    
    char strcat(char dst, const char src)
    {
    char ret = dst;
    
    
    
    while(*dst != '\0')
    
    ++dst;
    
    
    
    while((*dst++ = *src) != '\0');
    
    return ret;
    
    }
    /*手寫strcmp函數*/
    
    int strcmp(const char str1, const char str2)
    {
    
    
    
    while(*str1 == *str2 && *str1 != '\0')
    
    {
    
    ++str1;
    
    ++str2;
    
    }
    
    return *str1 - *str2;
    
    }

     

### [**Hash表**](http://blog.csdn.net/shanghairuoxiao/article/details/73693458)

- Hash表實現(拉鍊和分散地址)

- Hash策略常見的有哪些?

- STL中hash_map擴容發生什麼?

(1) 創建一個新桶,該桶是原來桶兩倍大最接近的質數(判斷n是不是質數的方法:用n除2到$sqrt(n)$範圍內的數) ;

(2) 將原來桶裏的數通過指針的轉換,插入到新桶中(注意STL這裏做的很精細,沒有直接將數據從舊桶遍歷拷貝數據插入到新桶,而是通過指針轉換)

(3) 通過swap函數將新桶和舊桶交換,銷燬新桶。

 

### [**樹**](http://blog.csdn.net/shanghairuoxiao/article/details/73752446)

- 二叉樹結構,二叉查找樹實現;

- 二叉樹的六種遍歷;

- 二叉樹的按層遍歷;

- 遞歸是解決二叉樹相關問題的神級方法;

- 樹的各種常見算法題(http://blog.csdn.net/xiajun07061225/article/details/12760493);

 

- **什麼是紅黑樹?**

- 節點爲紅色或者黑色;

- 根節點爲黑色;

- 從根節點到每個葉子節點經過的黑色節點個數的和相同;

- 如果父節點爲紅色,那麼其子節點就不能爲紅色。

 

- **紅黑樹與AVL樹的區別**

- 紅黑樹與AVL樹都是平衡樹,但是AVL是完全平衡的(平衡就是值樹中任意節點的左子樹和右子樹高度差不超過1);

- 紅黑樹效率更高,因爲AVL爲了保證其完全平衡,插入和刪除的時候在最壞的情況下要旋轉logN次,而紅黑樹插入和刪除的旋轉次數要比AVL少。

 

- **[Trie樹(字典樹)](http://blog.csdn.net/hackbuteer1/article/details/7964147)**

- 每個節點保存一個字符

- 根節點不保存字符

- 每個節點最多有n個子節點(n是所有可能出現字符的個數)

- 查詢的複雜父爲O(k),k爲查詢字符串長度

 

### **鏈表**

- 鏈表和插入和刪除,單向和雙向鏈表都要會

- 鏈表的問題考慮多個指針和遞歸

(1) 反向打印鏈表(遞歸)

(2) 打印倒數第K個節點(前後指針)

(3) 鏈表是否有環(快慢指針)等等。b ggg

 

### **棧和隊列**

- **隊列和棧的區別**?(從實現,應用,自身特點多個方面來闡述,不要只說一個先入先出,先入後出,這個你會別人也會,要展現出你比別人掌握的更深)

- 典型的應用場景

 

### **海量數據問題**

- 十億整數(隨機生成,可重複)中前K最大的數

類似問題的解決方法思路:首先哈希將數據分成N個文件,然後對每個文件建立K個元素最小/大堆(根據要求來選擇)。最後將文件中剩餘的數插入堆中,並維持K個元素的堆。最後將N個堆中的元素合起來分析。可以採用歸併的方式來合併。在歸併的時候爲了提高效率還需要建一個N個元素構成的最大堆,先用N個堆中的最大值填充這個堆,然後就是彈出最大值,指針後移的操作了。當然這種問題在現在的互聯網技術中,一般就用map-reduce框架來做了。

大數據排序相同的思路:先哈希(哈希是好處是分佈均勻,相同的數在同一個文件中),然後小文件裝入內存快排,排序結果輸出到文件。最後建堆歸併。

 

- **十億整數(隨機生成,可重複)中出現頻率最高的一千個**

 

### [**排序算法**](http://blog.csdn.net/shanghairuoxiao/article/details/74063684)

- 排序算法當然是基礎內容了,必須至少能快速寫出,快排,建堆,和歸併

- 每種算法的時間空間複雜度,最好最差平均情況

 

### [**位運算**](http://blog.csdn.net/shanghairuoxiao/article/details/75386508)

 

### 布隆過濾器

幾十億個數經常要查找某一個數在不在裏面,使用布隆過濾器,布隆過濾器的原理。布隆過濾器可能出現誤判,怎麼保證無誤差?

 

 

#### **安全相關**

- SQL注入

- XSS

- RCFS

- APR欺騙

 

#Linux

(1) 進程與線程區別?

(2) 線程比進程具有哪些優勢?

(3) 什麼時候用多進程?什麼時候用多線程?

(4) LINUX中進程和線程使用的幾個函數?

(5) 線程同步?

在Windows下線程同步的方式有:互斥量,信號量,事件,關鍵代碼段

在Linux下線程同步的方式有:互斥鎖,自旋鎖,讀寫鎖,屏障(併發完成同一項任務時,屏障的作用特別好使)

知道這些鎖之間的區別,使用場景?

 

### [進程間通訊方式]

管道( pipe,匿名管道 ):管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關係的進程間使用。進程的親緣關係通常是指父子進程關係。

命名管道 (FIFO) : 有名管道也是半雙工的通信方式,但是它允許無親緣關係進程間的通信。

信號量:信號量用於實現進程間的互斥與同步,而不是用於存儲進程間通信數據,有XSI信號量和POSIX信號量,POSIX信號量更加完善。

消息隊列( message queue ): 消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。

共享內存( shared memory ) :共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號兩,配合使用,來實現進程間的同步和通信。(原理一定要清楚,常考)

信號 ( sinal ) : 信號是一種比較複雜的通信方式,用於通知接收進程某個事件已經發生,常見的信號。

套接字( socket ) : 套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同及其間的進程通信。

匿名管道與命名管道的區別:匿名管道只能在具有公共祖先的兩個進程間使用。

共享文件映射mmap:mmap建立進程空間到文件的映射,在建立的時候並不直接將文件拷貝到物理內存,同樣採用缺頁終端。mmap映射一個具體的文件可以實現任意進程間共享內存,映射一個匿名文件,可以實現父子進程間共享內存。

 

常見的信號有哪些?

SIGINT,SIGKILL(不能被捕獲),SIGTERM(可以被捕獲),SIGSEGV,SIGCHLD,SIGALRM

 

### [**內存管理**](http://blog.csdn.net/shanghairuoxiao/article/details/70256247)

1. 虛擬內存的作用?

2. 虛擬內存的實現?

3. 操作系統層面對內存的管理?

4. 內存池的作用?STL裏[內存池如何實現](https://github.com/oscarwin/MemoryPool)?

5. 進程空間和內核空間對內存的管理不同?

6. Linux的slab層,VAM?

7. 夥伴算法

8. 高端內存

 

### 進程調度

1. Linux進程分爲兩種,實時進程和非實時進程;

2. 優先級分爲靜態優先級和動態優先級,優先級的範圍;

3. 調度策略,FIFO,LRU,時間片輪轉

4. 交互進程通過平均睡眠時間而被獎勵;

 

### [**死鎖**](http://blog.csdn.net/shanghairuoxiao/article/details/70444940)

(1) 死鎖產生的條件;

(2) 死鎖的避免;

 

### 命令行

- Linux命令 在一個文件中,倒序打印第二行前100個大寫字母

cat filename | head -n 2 | tail -n 1 | grep '[[:upper:]]' -o | tr -d '\n'| cut -c 1-100 | rev

 

- 與CPU,內存,磁盤相關的命令(top,free, df, fdisk)

 

- 網絡相關的命令netstat,tcpdump等

 

- sed, awk, grep三個超強大的命名,分別用與格式化修改,統計,和正則查找

 

- ipcs和ipcrm命令

 

- 查找當前目錄以及字母下以.c結尾的文件,且文件中包含"hello world"的文件的路徑

 

- 創建定時任務

 

### IO模型

- **五種IO模型:**阻塞IO,非阻塞IO,IO複用,信號驅動式IO,異步IO

 

- **select,poll,epoll的區別**

 

**select:**是最初解決IO阻塞問題的方法。用結構體fd_set來告訴內核監聽多個文件描述符,該結構體被稱爲描述符集。由數組來維持哪些描述符被置位了。對結構體的操作封裝在三個宏定義中。通過輪尋來查找是否有描述符要被處理,如果沒有返回**

存在的問題:

1. 內置數組的形式使得select的最大文件數受限與FD_SIZE;

2. 每次調用select前都要重新初始化描述符集,將fd從用戶態拷貝到內核態,每次調用select後,都需要將fd從內核態拷貝到用戶態;

3. 輪尋排查當文件描述符個數很多時,效率很低;

 

**poll:**通過一個可變長度的數組解決了select文件描述符受限的問題。數組中元素是結構體,該結構體保存描述符的信息,每增加一個文件描述符就向數組中加入一個結構體,結構體只需要拷貝一次到內核態。poll解決了select重複初始化的問題。輪尋排查的問題未解決。**

 

**epoll:**輪尋排查所有文件描述符的效率不高,使服務器併發能力受限。因此,epoll採用只返回狀態發生變化的文件描述符,便解決了輪尋的瓶頸。

- 爲什麼使用IO多路複用,最主要的原因是什麼?

- epoll有兩種觸發模式?這兩種觸發模式有什麼區別?編程的時候有什麼區別?

- 上一題中編程的時候有什麼區別,是在邊緣觸發的時候要把套接字中的數據讀乾淨,那麼當有多個套接字時,在讀的套接字一直不停的有數據到達,如何保證其他套接字不被餓死(面試網易遊戲的時候問的一個問題,答不上來,印象賊深刻)。

 

1. [select/poll/epoll區別](https://segmentfault.com/a/1190000003063859)

2. [幾種網絡服務器模型的介紹與比較](https://www.ibm.com/developerworks/cn/linux/l-cn-edntwk/index.html?ca=drs-)

3. [epoll爲什麼這麼快](http://www.jianshu.com/p/b5bc204da984)(搞懂這篇文章,關於IO複用的問題就信手拈來了)

 

### 線程池

 

### Linux的API

- **fork與vfork區別**

fork和vfork都用於創建子進程。但是vfork創建子進程後,父進程阻塞,直到子進程調用exit()或者excle()。

對於內核中過程fork通過調用clone函數,然後clone函數調用do_fork()。do_fork()中調用copy_process()函數先複製task_struct結構體,然後複製其他關於內存,文件,寄存器等信息。fork採用寫時拷貝技術,因此子進程和父進程的頁表指向相同的頁框。但是vfork不需要拷貝頁表,因爲父進程會一直阻塞,直接使用父進程頁表。

 

- **exit()與_exit()區別**

exit()清理後進入內核,_exit()直接陷入內核。

 

- 孤兒進程與僵死進程

1. 孤兒進程是怎麼產生的?

2. 僵死進程是怎麼產生的?

3. 僵死進程的危害?

4. 如何避免僵死進程的產生?

 

- **Linux是如何避免內存碎片的**

1. 夥伴算法,用於管理物理內存,避免內存碎片;

2. 高速緩存Slab層用於管理內核分配內存,避免碎片。

 

- **共享內存的實現原理?**

共享內存實現分爲兩種方式一種是採用mmap,另一種是採用XSI機制中的共享內存方法。mmap是內存文件映射,將一個文件映射到進程的地址空間,用戶進程的地址空間的管理是通過vm_area_struct結構體進行管理的。mmap通過映射一個相同的文件到兩個不同的進程,就能實現這兩個進程的通信,採用該方法可以實現任意進程之間的通信。mmap也可以採用匿名映射,不指定映射的文件,但是隻能在父子進程間通信。XSI的內存共享實際上也是通過映射文件實現,只是其映射的是一種特殊文件系統下的文件,該文件是不能通過read和write訪問的。

 

二者區別:

> 1、 系統V共享內存中的數據,從來不寫入到實際磁盤文件中去;而通過mmap()映射普通文件實現的共享內存通信可以指定何時將數據寫入磁盤文件中。注:前面講到,系統V共享內存機制實際是通過映射特殊文件系統shm中的文件實現的,文件系統shm的安裝點在交換分區上,系統重新引導後,所有的內容都丟失。

 

> 2、 系統V共享內存是隨內核持續的,即使所有訪問共享內存的進程都已經正常終止,共享內存區仍然存在(除非顯式刪除共享內存),在內核重新引導之前,對該共享內存區域的任何改寫操作都將一直保留。

 

> 3、 通過調用mmap()映射普通文件進行進程間通信時,一定要注意考慮進程何時終止對通信的影響。而通過系統V共享內存實現通信的進程則不然。注:這裏沒有給出shmctl的使用範例,原理與消息隊列大同小異。

 

 系統調用與庫函數(open, close, create, lseek, write, read)

 

-同步方法有哪些?

1. 互斥鎖,自旋鎖,信號量,讀寫鎖,屏障

2. 互斥鎖與自旋鎖的區別:互斥鎖得不到資源的時候阻塞,不佔用cpu資源。自旋鎖得不到資源的時候,不停的查詢,而然佔用cpu資源。

3. [死鎖](http://blog.csdn.net/shanghairuoxiao/article/details/70444940)

 

### 其他

 

 ++i是否是原子操作

明顯不是,++i主要有三個步驟,把數據從內存放在寄存器上,在寄存器上進行自增,把數據從寄存器拷貝會內存,每個步驟都可能被中斷。

 

設計模式

分佈式系統

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