拼多多、騰訊 C++開發工程師面試題

拼多多、騰訊 C++開發工程師面試題

(一)拼多多實習服務端

1、 一個C++源文件從文本到可執行文件經歷的過程

對於C/C++編寫的程序,從源代碼到可執行文件,一般經過下面四個步驟:

1).預處理,產生.ii文件

2).編譯,產生彙編文件(.s文件)

3).彙編,產生目標文件(.o或.obj文件)

4).鏈接,產生可執行文件(.out或.exe文件)

2、#include 的順序以及尖叫括號和雙引號的區別

1. #include的順序的區別:

頭文件的引用順序對於程序的編譯還是有一定影響的。如果要在文件a.h中聲明一個在文件b.h中定義的變量,而不引用b.h。那麼要在a.c文件中引用b.h文件,並且要先引用b.h,後引用a.h,否則彙報變量類型未聲明錯誤,也就是常見的某行少個“;”符號。

2. #include尖括號和雙引號的區別:

1)#include <> ,認為該頭文件是標準頭文件。編譯器將會在預定義的位置集查找該頭文件,這些預定義的位置可以通過設置查找路徑環境變量或者通過命令行選項來修改。使用的查找方式因編譯器的不同而差別迥異。

2)#include "",認為它是非系統頭文件,非系統頭文件的查找通常開始於源文件所在的路徑。查找範圍大於<>。

3、進程和線程,為什麼要有線程

1、和進程相比,它是一種非常"節儉"的多任務操作方式。在linux系統下,啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的數據表來維護它的代碼段、堆棧段和數據段,這是一種"昂貴"的多任務工作方式。(資源)

2、運行於一個進程中的多個線程,它們之間使用相同的地址空間,而且線程間彼此切換所需時間也遠遠小於進程間切換所需要的時間。據統計,一個進程的開銷大約是一個線程開銷的30倍左右。(切換效率)

3、線程間方便的通信機制。對不同進程來說,它們具有獨立的數據空間,要進行數據的傳遞只能通過進程間通信的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由於同一進城下的線程之間貢獻數據空間,所以一個線程的數據可以直接為其他線程所用,這不僅快捷,而且方便。(通信)

除以上優點外,多線程程序作為一種多任務、併發的工作方式,還有如下優點:

1、使多CPU系統更加有效。操作系統會保證當線程數不大於CPU數目時,不同的線程運行於不同的CPU上。(CPU設計保證)

2、改善程序結構。一個既長又複雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程序才會利於理解和修改。(代碼易維護)

4、C++11有哪些新特性

1)關鍵字及新語法:auto、nullptr、for

2)STL容器:std::array、std::forward_list、std::unordered_map、std::unordered_set

3)多線程:std::thread、std::atomic、std::condition_variable

4)智能指針內存管理:std::shared_ptr、std::weak_ptr

5)其他:std::function、std::bind和lamda表達式

5、為什麼可變參數模板至關重要,右值引用,完美轉發,lambda

6、malloc的原理,brk系統調用幹什麼的,mmap呢

malloc的實現方案:

1)malloc 函數的實質是它有一個將可用的內存塊連接為一個長長的列表的所謂空閒鏈表。

2)調用 malloc()函數時,它沿著連接表尋找一個大到足以滿足用戶請求所需要的內存塊。 然後,將該內存塊一分為二(一塊的大小與用戶申請的大小相等,另一塊的大小就是剩下來的字節)。 接下來,將分配給用戶的那塊內存存儲區域傳給用戶,並將剩下的那塊(如果有的話)返回到連接表上。

3)調用 free 函數時,它將用戶釋放的內存塊連接到空閒鏈表上。

4)到最後,空閒鏈會被切成很多的小內存片段,如果這時用戶申請一個大的內存片段, 那麼空閒鏈表上可能沒有可以滿足用戶要求的片段了。於是,malloc()函數請求延時,並開始在空閒鏈表上檢查各內存片段,對它們進行內存整理,將相鄰的小空閒塊合併成較大的內存塊。

brk和mmap:

從操作系統角度來看,進程分配內存有兩種方式,分別由兩個系統調用完成:brk和mmap(不考慮共享內存)。

1、brk是將數據段(.data)的最高地址指針_edata往高地址推;

2、mmap是在進程的虛擬地址空間中(堆和棧中間,稱為文件映射區域的地方)找一塊空閒的虛擬內存。

這兩種方式分配的都是虛擬內存,沒有分配物理內存。在第一次訪問已分配的虛擬地址空間的時候,發生缺頁中斷,操作系統負責分配物理內存,然後建立虛擬內存和物理內存之間的映射關係。

在標準C庫中,提供了malloc/free函數分配釋放內存,這兩個函數底層是由brk,mmap,munmap這些系統調用實現的。

7、C++的內存管理方式,STL的allocator,最新版本默認使用的分配器

C++的內存管理方式:

在c++中內存主要分為5個存儲區:

棧(Stack):局部變量,函數參數等存儲在該區,由編譯器自動分配和釋放.棧屬於計算機系統的數據結構,進棧出棧有相應的計算機指令支持,而且分配專門的寄存器存儲棧的地址,效率分高,內存空間是連續的,但棧的內存空間有限。

堆(Heap):需要程序員手動分配和釋放(new,delete),屬於動態分配方式。內存空間幾乎沒有限制,內存空間不連續,因此會產生內存碎片。操作系統有一個記錄空間內存的鏈表,當收到內存申請時遍歷鏈表,找到第一個空間大於申請空間的堆節點,將該節點分配給程序,並將該節點從鏈表中刪除。一般,系統會在該內存空間的首地址處記錄本次分配的內存大小,用於delete釋放該內存空間。

全局/靜態存儲區:全局變量,靜態變量分配到該區,到程序結束時自動釋放,包括DATA段(全局初始化區)與BSS段(全局未初始化段)。其中,初始化的全局變量和靜態變量存放在DATA段,未初始化的全局變量和靜態變量存放在BSS段。BSS段特點:在程序執行前BSS段自動清零,所以未初始化的全局變量和靜態變量在程序執行前已經成為0.

文字常量區:存放常量,而且不允許修改。程序結束後由系統釋放。

程序代碼區:存放程序的二進制代碼

SGI 版本STL的默認配置器std::alloc

參見:《STL源碼剖析》

1)考慮到小型區塊所可能造成的內存碎片問題,SGI設計了雙層配置器。第一級配置器直接使用malloc()和free();第二級則視情況採取不同的策略:當配置區塊超過128bytes時,視為“足夠大”,便調用第一級配置器;當配置區塊小於128bytes時,視之為“過小”,為了降低額外負擔,便採用memory pool(內存池)整理方式,而不在求助於第一級配置器。

2)內存池的核心:內存池和16個自由鏈表(各自管理8,16,...,128bytes的小額區塊)。在分配一個小區塊時,首先在所屬自由鏈表中尋找,如果找到,直接抽出分配;若所屬自由鏈表為空,則請求內存池為所屬自由鏈表分配空間;默認情況下,為該自由鏈表分配20個區塊,若內存池剩餘容量不足,則分配可分配的最大容量;若內存池連一個區塊都無法分配,則調用chunk_alloc為內存池分配一大塊區塊;若內存不足,則嘗試調用malloc分配,否則返回bad_alloc異常。

8、hash表的實現,包括STL中的哈希桶長度常數。

hash表的實現主要涉及兩個問題:散列函數和碰撞處理。

1)hash function (散列函數)。最常見的散列函數:f(x) = x % TableSize .

2)碰撞問題(不同元素的散列值相同)。解決碰撞問題的方法有許多種,包括線性探測、二次探測、開鏈等做法。SGL版本使用開鏈法,使用一個鏈表保持相同散列值的元素。

雖然開鏈法並不要求表格大小必須為質數,但SGI STL仍然以質數來設計表格大小,並且將28個質數(逐漸呈現大約兩倍的關係)計算好,以備隨時訪問,同時提供一個函數,用來查詢在這28個質數之中,“最接近某數並大於某數”的質數。

9、hash表如何rehash,怎麼處理其中保存的資源

先想想為什麼需要rehash:

因為,當loadFactor(負載因子)<=1時,hash表查找的期望複雜度為O(1). 因此,每次往hash表中添加元素時,我們必須保證是在loadFactor <1的情況下,才能夠添加。

模仿C++的vector擴容方式,Hash表中每次發現loadFactor==1時,就開闢一個原來桶數組的兩倍空間(稱為新桶數組),然後把原來的桶數組中元素全部轉移過來到新的桶數組中。注意這裡轉移是需要元素一個個重新哈希到新桶中的。



拼多多、騰訊 C++開發工程師面試題


(二)騰訊二面面經

1、redis的主從複製怎麼做的

Redis舊版複製功能只有同步和命令傳播。新版複製功能加入了部分同步的功能。

1)同步:

2)命令傳播:

當主服務器會將自己執行的寫命令,也即是造成主從服務器不一致的那條寫命令,發送給從服務器執行,當從服務器執行了相同的寫命令之後,主從服務器將再次回到一致狀態。

3)部分同步:(斷線後重複製)

複製偏移量:通過對比主從服務器的複製偏移量,程序可以很容易地知道主從服務器是否處於一致狀態。

複製積壓緩衝區:主服務保存最近的寫命令到複製積壓緩衝區,是一個先進先出隊列

服務器運行ID:從服務器記錄上次同步的主服務器的Id。

2、寫代碼,去掉字符串中的空格空格

 #include <iostream>
using namespace std;
int main()
{
char str[40] = " abc 123 456 ";
int num = 0;
int i;
for(i = 0; str[i] != '\\0'; ++i)
{
if(str[i] == ' ')
++num;
else
str[i-num] = str[i];
}
str[i-num] = '\\0';
printf("%s\\n",str);
}
/<iostream>

3、如何把一個文件快速下發到100w個服務器

gossip算法?Gossip有眾多的別名“閒話算法”、“疫情傳播算法”、“病毒感染算法”、“謠言傳播算法”。

4、如何判斷一個圖是否連同?

DFS、BFS、並查集

5、ubuntu開機的時候系統做了什麼

1)加載BIOS

BIOS程序首先檢查,計算機硬件能否滿足運行的基本條件,這叫做”硬件自檢”。硬件自檢完成後,BIOS把控制權轉交給下一階段的啟動程序。

2)讀取MBR

計算機讀取該設備的第一個扇區,也就是讀取最前面的512個字節。如果這512個字節的最後兩個字節是0x55和0xAA,表明這個設備可以用於啟動;如果不是,表明設備不能用於啟動,控制權於是被轉交給”啟動順序”中的下一個設備。

3)Bootloader

在這種情況下,計算機讀取”主引導記錄”前面446字節的機器碼之後,不再把控制權轉交給某一個分區,而是運行事先安裝的”啟動管理器”(boot loader),由用戶選擇啟動哪一個操作系統。

Boot Loader 就是在操作系統內核運行之前運行的一段小程序。通過這段小程序,我們可以初始化硬件設備、建立內存空間的映射圖,從而將系統的軟硬件環境帶到一個合適的狀態,以便為最終調用操作系統內核做好一切準備。

Boot Loader有若干種,其中Grub、Lilo和spfdisk是常見的Loader。Linux環境中,目前最流行的啟動管理器是Grub。

4)加載內核

內核的加載,內核加載後,接開始操作系統初始化,根據進程的優先級啟動進程。

因文章篇幅過長,所以不能一一上傳,如有想獲取完整資料或者技術交流和學習的可以私信我

拼多多、騰訊 C++開發工程師面試題

資料獲取方式:

關注+轉發後,私信關鍵詞 【面試】即可獲取!

重要的事情說三遍,轉發、轉發、轉發後再發私信,才可以拿到!

關注+轉發後,私信關鍵詞 【面試】即可獲得詳細答案鏈接!


分享到:


相關文章: