??一、C++ 語言基礎與底層原理
請解釋?new
?/?delete
?和?malloc
?/?free
?的區別和聯系,以及使用它們時需要注意什么
|
簡述 C++ 的內存分區(棧、堆、全局/靜態存儲區、常量存儲區、代碼區)以及對象在這些區域的創建過程。
棧區:自動分配/釋放(函數調用時創建,返回時銷毀) 堆區:手動分配/釋放 全局/靜態存儲區:程序啟動時分配,程序結束時釋放 常量存儲區:只讀內存,生命周期與程序相同 代碼區:只讀內存 |
移動語義 (std::move
) 和完美轉發 (std::forward
) 解決了什么問題?它們的實現原理(引用折疊)是什么?在什么情況下使用?(RValue Reference, Universal Reference)
移動語義:允許資源所有權轉移(而非復制),顯著提升資源管理效率 完美轉發:模板函數參數轉發時丟失值類別(左值/右值)和 CV 屬性 保持參數的原始值類別(左值/右值)和類型屬性(const/volatile) |
面向對象:??
詳細解釋 C++ 中的虛函數機制(vptr, vtable)。
虛函數表:編譯器為每個??包含虛函數的類??生成的??靜態函數指針數組? vptr:編譯器自動添加到??每個對象實例??中的??隱藏指針? |
多重繼承下菱形繼承(鉆石問題)是如何產生的?C++ 如何通過虛繼承來解決它?(涉及 virtual base class pointer, vtordisp 等,至少講清概念和原理)
? ? ? A
? ? ? A 使用虛繼承 class A { /* ... */ }; // Base class class B : public virtual A { /* ... */ }; // Virtual inheritance from A class D : public B, public C { /* ... */ }; // Regular multiple inheritance 虛繼承如何解決菱形繼承問題:??
|
面向對象的三大特性(封裝、繼承、多態)在 C++ 中是如何體現的?
核心機制??:通過 類型支持??:
實現基礎??:虛函數表(vtable)+ 動態綁定 |
?并發與同步:??
解?std::thread
,?std::mutex
,?std::lock_guard
,?std::unique_lock
,?std::condition_variable
?的用法和區別。
thread:用于創建和管理新的執行線程,代表一個執行單元 包含以下成員函數 ? ? ? ? join():阻塞調用函數,通常為主線程,等待*this線程執行結束 ? ? ? ? detach():分離*this線程,與thread對象解耦,允許其在后臺允許 ? ? ? ? get_id() 獲取線程標識符 mutex:提供基本的獨占互斥所有權語義,防止多個線程訪問共享數據,避免數據競爭 ? ? ? ? lock():嘗試獲取互斥鎖,如果互-互斥鎖已經被其它線程持有,則阻塞 ? ? ? ? unlock():釋放互斥鎖 ? ? ? ? try_lock():嘗試獲取互斥鎖,如果鎖不可用立即返回 lock_guard:自動管理mutex的鎖定和解鎖:構造時自動lock關聯的互斥量,析構時自動unlock unique_lock:相較于lock_guard更將靈活,且增加了更多的特性 ? ? ? ? 支持延遲上鎖 ? ? ? ? 支持嘗試鎖定 ? ? ? ? 支持多次鎖定和解鎖 ? ? ? ? 支持條件變量:condition_variable ? ? ? ? 可移動但不可復制 下面給出unique_lock使用的一個示例
|
std::mutex mtx;
std::condition_variable cv;
bool data_ready = false;// 生產者線程
void producer() {{std::unique_lock<std::mutex> lock(mtx); // 上鎖// ...生產數據...data_ready = true;} // unique_lock 這里可能解鎖,更晚些也行cv.notify_one(); // 通知消費者
}// 消費者線程
void consumer() {std::unique_lock<std::mutex> lock(mtx); // 構造時上鎖// 關鍵:wait 會在內部原子地解鎖 mtx 并使線程阻塞等待通知// 收到通知后(可能虛假喚醒),wait 會重新嘗試獲取鎖cv.wait(lock, []{ return data_ready; }); // 直到 data_ready 為真// 此時 lock 持有鎖,數據安全可用// ...消費數據...data_ready = false;
} // 解鎖
std::atomic
?解決了什么問題?它相比直接使用鎖的優勢和局限在哪里?
解決了無鎖或低鎖并發編程中的核心問題,安全,高效的在多線程環境下訪問和修改共享數據,而無需每次都使用重量級別的互斥鎖 相較于直接使用鎖:高頻輕量操作,無阻塞操作,更少的緩存行競爭 局限于:使用場所有限,存在ABA問題,而且由于較弱的內存序可能會導致非原子變量的讀寫操作被CPU重新排序到錯誤的位置,破壞程序邏輯 |
簡述無鎖編程(Lock-Free)的思想以及 CAS 操作。無鎖隊列的基本實現思路?
無鎖編程是一種??并發編程范式??,其核心目標是??設計在多線程環境下無需使用傳統互斥鎖(如? 無鎖算法的關鍵特征是:??即使某些線程被任意延遲(如被操作系統掛起、發生頁錯誤),也至少有一個線程能夠取得進展(完成操作)??。 樂觀并發控制 依賴硬件原子指令 提高并發性和可伸縮性 CAS思想偽代碼描述如下 bool compare_and_swap(T* ptr, T expected, T new_value) { 無鎖隊列 struct Node { class LockFreeQueue { |
二、 操作系統與網絡
?操作系統原理:??
Linux 進程間通信(IPC)有哪些主要方式?對比它們的優缺點和適用場景。
管道:最簡單的IPC形式,在??具有親緣關系(通常是父子或兄弟)?? 的進程間創建單工(半雙工)字節流通道。數據寫入管道的寫入端,從讀取端順序讀取(FIFO)。 命名管道:突破管道必須具有親緣關系的限制,允許??任何進程(甚至無親緣關系)?? 通過打開這個“文件”名進行通信。遵循FIFO原則。 消息隊列:在內核中維護的??消息鏈表??。進程可以向隊列添加??結構化的消息??(有類型和負載數據)或從隊列中讀取特定類型的消息。消息具有優先級(POSIX)或類型(SysV)。 共享內存:??速度最快的IPC方式??!內核將同一塊物理內存映射到多個進程各自的用戶空間地址范圍。進程可以直接讀寫這塊內存,就像訪問自己的內存一樣,??無需內核介入拷貝??。 信號量:它是一個用于??同步多個進程??(或線程)對??共享資源??(如共享內存區域、文件、硬件設備)訪問的計數器。基本操作是PV操作(wait/P - 申請資源減小計數,signal/V - 釋放資源增加計數)。 socket:最強大、最通用的IPC/RPC機制??。支持不同主機(網絡IPC)或同一主機(Unix Domain Socket)上進程間通信。 |
Linux 中文件描述符(File Descriptor)的本質是什么?select/poll/epoll I/O 多路復用的區別和各自的優勢?epoll 的水平觸發(LT)和邊沿觸發(ET)模式區別?
Linux中的文件描述符不是文件本身,也不是指向文件內容的指針,其為一個非負整數,代表一個進程級別打開文件表的條目索引,每個進程都存在一個打開文件表,當進程打開一個文件的時候,內核會在這個表中創建一個條目,這個表中存在指向系統級別打開文件表的指針,文件的訪問模式等,同時存在一個系統打開文件表,每個條目代表一個真正被打開的文件實例,時間上,這個文件描述符就是已打開IO資源的句柄 select:存在數量限制:1024 || 這里敘述一下這個的流程,調用select,內核掃描用戶傳入fd_set中的所有fd,檢查它們的狀態,隨后當有fd超時或者就緒,標記,全部標記完返回,用戶再次掃描 poll:長度可變化,事件分離 epoll:內核使用紅黑樹組織列表,使用雙向鏈表管理就緒列表,內核使用回調極值僅在fd狀態發生變化的時候將其加入就緒列表,返回值只拷貝就緒的事件信息,使用一個單獨的函數添加修改刪除內核需要剪視的fd,使得每次不需要傳入所有需要監視的fd,監視列表在內核中維護 水平觸發:只要fd緩沖區處于就緒狀態,就會持續報告這個事件 邊緣觸發:只在fd緩沖區狀態發生改變的時候,才會進行報告 |
用戶態和內核態切換的開銷在哪里?
首先介紹一下用戶態和內核態 用戶態:應用程序運行的環境,CPU在這個狀態指向的指令受限,不能訪問特權硬件資源 內核態:操作系統內核運行的環境,CPU在這個狀態下擁有最高權限 開銷主要來源于以下幾個方面 硬件上下文保存和恢復==(通用寄存器,指令指針,棧指針,狀態寄存器) CPU流水線,緩存以及分支預測失效==流水線刷新,TLB失效,Cache污染(內核代碼將熱點數據擠出緩存),分支預測干擾 |
進程的虛擬地址空間布局是怎樣的?
現代操作系統為每個進程提供一個私有的,連續的,獨立的虛擬地址空間 一般從高地址到低地址部分為 內核空間?操作系統內核的代碼、數據和每個進程的內核棧(處理系統調用/中斷)、頁表等關鍵數據結構。注意,所有進程共享同一份內核空間映射 用戶棧 內存映射區?將文件內容映射到進程地址空間。這是? 用戶堆 未初始化數據段 .bss??存儲??全局/靜態變量 已初始化數據段 .data???顯式初始化的全局變量和靜態變量?? 代碼段? .text |
網絡協議:??
詳細描述一次完整的 HTTP 請求過程(從輸入 URL 到瀏覽器顯示,涉及 DNS、TCP 握手、HTTP 請求/響應、TLS 握手等)。
階段0: ????????用戶輸入URL ????????瀏覽器解析URL,提取關鍵信息,包括協議,主機名,端口號,未指定使用默認端口,路徑等等 ? ? ? ? 檢查緩存,瀏覽器檢查內置HSTS,沒有,進入下一步 階段1: ? ? ? ? 本地緩存未命中 ? ? ? ? 操作系統進行DNS查詢,操作系統查詢本地的DNS緩存,沒有 ? ? ? ? 將DNS請求發送配置的DNS遞歸解析器,如果其本地還沒有 ? ? ? ? 迭代查詢:順次查詢根域名服務器,頂級域名服務器,權威域名服務器,得到對應IP地址 ? ? ? ? 返回給操作系統,緩存 階段2:建立傳輸連接-TCP握手 階段3:如果使用的時https協議:進行TLS握手,流程寫在了下面 階段4:客戶端構造HTTP請求,發送給服務端,服務端收到,并返回 階段5:瀏覽器渲染 |
TLS握手流程:
1.TLS 第一次握手:客戶端向服務器發起加密通信請求,發送 ClientHello 消息。
客戶端主要發送以下信息:
支持的 TLS 協議版本,如 TLS 1.2。
客戶端生成的隨機數(Client Random),用于生成「會話密鑰」的一部分。
客戶端支持的密碼套件列表,如 RSA 加密算法。
2.TLS 第二次握手:服務器收到客戶端請求后,向客戶端發送 ServerHello 消息作為響應。
服務器回應的內容包括:
確認的 TLS 協議版本,如果瀏覽器不支持,則關閉加密通信。
服務器生成的隨機數(Server Random),用于生成「會話密鑰」的一部分。
確認的密碼套件列表,如 RSA 加密算法。
服務器的數字證書(Certificate)。
3.TLS 第三次握手:客戶端收到服務器的響應后,首先通過 CA 公鑰確認服務器的數字證書的真實性。
如果證書驗證通過,客戶端從數字證書中取出服務器的公鑰,并用該公鑰加密報文,向服務器發送以下信息:
一個隨機數(pre-master key),該隨機數將被服務器的私鑰解密。
加密通信算法改變通知,表示隨后的信息都將使用「會話密鑰」加密通信。
客戶端握手結束通知,表示客戶端的握手階段已經結束。
4.TLS 第四次握手:服務器收到客戶端的第三次握手消息后,通過協商的加密算法,計算出本次通信的「會話密鑰」。
然后,服務器向客戶端發送以下信息:
加密通信算法改變通知,表示隨后的信息都將使用「會話密鑰」加密通信。
服務器握手結束通知,表示服務器的握手階段已經結束。
客戶端和服務器的握手階段全部結束,建立安全連接,可以開始進行加密通信。
TLS1.3改進了上述行為
首先是直接發送客戶端公鑰
然后服務端恢復證書簽名和自己的公鑰
這樣雙方就都持有各自的公鑰可以進行加密了
TCP 和 UDP 的核心區別是什么?TCP 如何保證可靠傳輸(序列號、確認應答、超時重傳、流量控制、擁塞控制)?
面向連接和無連接 可靠性和盡力而為 TCP傳遞字節流,UDP傳遞消息/數據報 TCP有流量控制,擁塞控制,超時重傳,確認應答,序列號等復雜機制,UDP沒有 |
解釋 TCP 的三次握手和四次揮手過程,為什么是三次不是兩次?為什么揮手需要四次?TIME_WAIT 狀態的作用是什么?
對于為什么是三次握手: 兩次握手情況下:我們假設這樣一種場景,由于網絡阻塞,客戶端發送的一個請求連接沒有到達,客戶端發送了第二個連接請求,此時第一個請求到達,服務器返回,并且認為自己同客戶端建立了連接,但是這個回復到達客戶端的時候,由于客戶端已經發送了新的連接就會丟棄,然后如果第二個請求到達,服務器以為這個是新的連接,就再次返回,此時客戶端收到后建立連接,但是這樣就造成服務端多了一個連接,所以我們采用三次握手,如果客戶端丟棄了這個回復,那么服務端就會因為超時釋放那個連接 同時第三次握手使得服務端知道客戶端同步了自己的序號 對于為什么是四次揮手:這個理由就很多了 全雙工需要雙向獨立關閉 被動關閉方需要處理遺留數據 主動關閉方需要確認對方的 FIN主動關閉方需要確認對方的 FIN
可靠地終止最后一個 ACK:確保被動關閉方能夠成功收到第四次揮手的ACK 消除舊連接報文干擾:使得此時網絡中屬于這個連接的報文全部過期 |
?三、數據庫與存儲
??數據庫:??
數據庫索引的原理(B+Tree 結構)?為什么索引能加快查詢速度?聚簇索引和非聚簇索引的區別?
B+Tree是平衡多叉樹 葉子結點都位于同一層,保證了任何查找操作從根結點到葉子的路徑長度基本相等 內部結點僅存儲索引鍵值,不存儲真實數據,這樣使得每個塊可以存儲更多的索引結點,從而減少了IO次數 葉子節點為雙向鏈表,可以實現順序查找 索引加速查詢的核心在于可以減少需要掃描的數據量以及利用數據的有序性 1. 避免了全表掃描 2. 利用有序性高效的定位 3. 減少了磁盤IO次數 聚簇索引(InnoDB)數據和索引一起存放 非聚簇索引(MyISAM)數據和索引單獨存放,需要進行回表,回表是隨機IO,性能會急劇下降 |
數據庫事務的 ACID 特性分別是什么含義?數據庫是如何通過日志(redo log, undo log)和鎖(共享鎖、排他鎖)來實現事務的?
A:原子性:事務中的所有操作要么全部成功,要么失敗回滾 C:一致性:事務執行的結果必須使數據庫從一個一致性狀態到另一個一致性狀態 I:隔離性:事務可以并發執行,但是一個事務的執行不能被其他事務干擾 D:持久性:一旦事務成功提交,其對數據庫的修改就是永久了,不會因為斷電崩潰而丟失 redo log:重做日志,保證事務的持久性,并在系統崩潰后用于恢復已經提交的事務,記錄的內容是對數據頁的物理修改 工作原理: 當事務修改數據的時候,首先發生在內存的Buffer Pool,同時數據庫引擎會生成一條或者多條 redo log record,用來描述這個物理修改 在臟頁(被修改但未寫入磁盤的數據頁)被刷新到磁盤數據文件之前,對于的redo log記錄必須先被陷入并刷新到持久化的redo log文件中 事務提交的時候,數據庫引擎會強制將所有與該事務相關的redo log刷新到磁盤中 提交操作本身通常也會記錄一條特殊的redo log 崩潰恢復:系統崩潰重啟后,數據庫進入恢復階段 1. 從redo log中讀取文件記錄 2. 從上次完成的Checkpoint開始進行提交 undo log:回滾日志,保證事務的原子性,用于事務回滾,支持隔離性與MVCC 記錄的是事務修改數據??之前??的??舊值和??邏輯操作 工作原理 當修改內存的數據頁,會先向undo log中寫入一條記錄,記錄修改前的舊值以及如何撤銷這個修改的信息,這條undo log也會被寫到磁盤上的undo log文件 如果事務需要回滾,數據庫引擎會根據該書屋對于的undo log記錄,逆向執行操作 MVCC(多版本并發控制),當一個讀操作開始的時候,數據庫會記錄當前系統版本號,數據庫會利用undo log重建改行在事務開始時的舊版數據,提供給讀操作,這樣讀操作就不會看到未提交修改 鎖的區分有很多 1.? 共享鎖(讀鎖):允許事務讀取數據項,其它事務也可以獲取該數據項的共享鎖 排他鎖(寫鎖):允許事務讀取和修改數據項,其余事務不允許獲取任何類型的鎖 2. 行級鎖 頁級鎖 表級鎖 3. 意向鎖:表級鎖,表示事務??打算??在表的更細粒度(頁或行)上加鎖。用于快速判斷表級沖突 |
?
四、 數據結構與算法
編碼能力:??
邏輯題)海量數據相關:如何從100億個URL中快速找出重復出現的URL?(分治、哈希、布隆過濾器)
方法1:哈希+分冶 設計一個哈希函數將每個URL映射到一個哈希值,根據哈希值將URL分配到多個小文件中,這樣相同的URL一定會被分配到同一個文件中,使得每個小文件的大小不超過內存限制 然后進行逐個文件處理 方法2:布隆過濾器 我們可以用布隆過濾器作為第一層過濾,然后對候選的URL再進行精確統計。 |