C++ 引用的本質是什麼?
①C++中的引用本質上是 一種被限制的指針(類似於線性表和棧,棧是被限制的線性表,底層實現相同,只不過邏輯上的用法不同而已)。
②由於引用是被限制的指針,所以引用是佔據內存的。
③在使用高級語言的層面上,是沒有提供訪問引用的方法的。並且引用創建時必需初始化,創建後還不能修改。
gdb 的原理
gdb 主要通過系統調用 ptrace 實現. ptrace 可以將 gdb attach 到指定線程, 這有兩種方式:
在 gdb 中運行一個進程, 再對其進行調試. 首先利用 fork 創建該進程, 再在子進程中調用 ptrace,
並將第一個參數設為 PTRACE_TRACEME, 表示此進程將被父進程跟蹤, 然後執行 exec 函數.
將 gdb attach 到一個已存在的進程. 指定進程的 pid, gdb 調用 ptrace, 將第一個參數設為 PTRACE_ATTACH,
將 pid 作為函數參數. 這樣, gdb 就成為該進程的父進程, 並跟蹤該進程.
通過這兩種方式, gdb 就和被調試進程建立了聯繫, 即成為其父進程, 該進程被父進程跟蹤.
此時任何傳遞給該進程的信號(除了SIGKILL)都將通過 wait 方法阻塞該進程, 並將信號轉交給父進程.
並且, 該進程如果調用 exec 函數, 都會接收到一個 SIGTRAP 信號, 使得父進程(gdb)可以在被跟蹤進程執行第一條指令前就可以做一些需要的工作.
這也是第一種實現方式的原理.
斷點*是通過內核信號實現的. 增加斷點時, 實際是將指定位置寫入指令 INT 3. 運行到此指令時,
會觸發 SIGTRAP 信號, 從而被跟蹤進程會被暫停, gdb可以捕獲到此信號. gdb將斷點組織為一個鏈表,
此時就可以查詢此鏈表, 檢查是否有匹配的斷點記錄, 當存在時就發生斷點命中, 就允許用戶做一些調試操作.
否則繼續執行命令.
編譯器會為const引用創建臨時變量
將常引用綁定到臨時數據時,
const int &A;
==編譯器會為臨時數據創建一個新的、無名的臨時變量,並將臨時數據放入臨時變量中,然後再將引用綁定到臨時變量。==臨時變量也是變量,所有的變量都會被分配內存。
常引用和普通引用不一樣。
因為臨時數據無法尋址,不能寫入。而引用是綁定到一份數據時,就可以通過引用對數據進行讀取和修改。即使為臨時數據創建一個臨時變量的時候,修改的也是臨時變量的數據,而不是源數據。這樣引用所綁定的數據和源數據不能同步更新,失去了操作數據的作用。
以代碼為例
void swap(int &r1, int &r2){
int temp = r1;
r1 = r2;
r2 = temp;
}
如果編譯器為r1和r2創建了臨時變量,那麼r1和r2的值怎樣都不會發生交換。
常引用只能通過const引用讀取數據的值,而不能修改數據的值。所以不用考慮數據更新的問題,也不會產生兩份數據。
以代碼為例
//該函數用來判斷數字是否為奇數
bool isOdd(const int &n){ //改為常引用
if(n/2 == 0){
return false;
}else{
return true;
}
}
int main(){
int a = 100;//
isOdd(a); //正確
isOdd(a + 9); //正確
isOdd(27); //正確
isOdd(23 + 55); //正確
}
對於第12行的代碼,編譯器不會創建臨時變量,引用n會直接綁定a,而對於13~15行代碼,編譯器會創建臨時變量來存儲臨時數據。即編譯器只有在必要的時候才會創建臨時變量。
IO複用的實現原理
IO設備的驅動程序中有一個等待隊列, 可以通過驅動程序提供的 poll 接口來獲取IO設備是否就緒, 也可以將進程加入到等待對應的等待隊列中.
當IO設備就緒時, 就可以通知等待隊列中的進程, 將其從睡眠中喚醒.
select, poll 的實現是:
掃描所有 fd, 利用 poll 接口檢測對應的IO是否就緒, 並將進程加入到等待隊列.
如果存在就緒的 fd, 就在遍歷後返回就緒 fd 的數量.
如果沒有就緒的 fd, 就睡眠一段時間.
如果休眠結束或被IO驅動程序喚醒, 繼續循環上面的過程. 如果是後者, 就會檢測到就緒的 fd, 從而可以在第二步返回.
epoll 流程也類似上面, 只有下面幾點不同:
epoll 在創建句柄時將 fd 集合拷貝到內核, epoll_wait 時不需要再拷貝 fd 集合, 這樣對同一集合多次調用 epoll_wait 時就只需要拷貝一次.
epoll 的句柄通過一個紅黑樹來維護 fd 集合, 每次加入時會先在紅黑樹中查找是否已經保存了該 fd,
時間複雜度是 log(N)log(N).
epoll 第一次也要遍歷 fd, 並將進程加入到等待隊列, 但是同時為每個 fd 指定了回調函數, 當 fd 就緒時,
設備驅動程序會喚醒進程, 並調用此回調函數. 這個回調函數的作用是將這個就緒的 fd 加入到就緒鏈表.
因此, 當進程從等待中被喚醒時, 就可以直接通過檢查這個就緒鏈表是否為空來判斷是否有 fd 就緒,
而不需要像 select/poll 一樣再次遍歷 fd 集合.
調用 epoll_wait 時, 會把就緒的 fd 拷貝到用戶態內存, 然後清空就緒鏈表, 最後再檢查這些 fd.
在水平觸發模式下, 如果檢查到這些 fd 上還有未處理的事件, 會將這些 fd 放回就緒鏈表中, 保證事件得到正確處理.
對於 fd 集合大小的限制, epoll 是進程可以打開的最大文件數目, 這個值保存在 /proc/sys/fs/file-max.
malloc的原理,另外brk系統調用和mmap系統調用的作用分別是什麼?
參考回答:
Malloc函數用於動態分配內存。為了減少內存碎片和系統調用的開銷,malloc其採用內存池的方式,先申請大塊內存作為堆區,然後將堆區分為多個內存塊,以塊作為內存管理的基本單位。當用戶申請內存時,直接從堆區分配一塊合適的空閒塊。Malloc採用隱式鏈表結構將堆區分成連續的、大小不一的塊,包含已分配塊和未分配塊;同時malloc採用顯示鏈表結構來管理所有的空閒塊,即使用一個雙向鏈表將空閒塊連接起來,每一個空閒塊記錄了一個連續的、未分配的地址。
當進行內存分配時,Malloc會通過隱式鏈表遍歷所有的空閒塊,選擇滿足要求的塊進行分配;當進行內存合併時,malloc採用邊界標記法,根據每個塊的前後塊是否已經分配來決定是否進行塊合併。
Malloc在申請內存時,一般會通過brk或者mmap系統調用進行申請。其中當申請內存小於128K時,會使用系統函數brk在堆區中分配;而當申請內存大於128K時,會使用系統函數mmap在映射區分配。
請你說一說C++的內存管理是怎樣的?
參考回答:
在C++中,虛擬內存分為代碼段、數據段、BSS段、堆區、文件映射區以及棧區六部分。
代碼段:包括只讀存儲區和文本區,其中只讀存儲區存儲字符串常量,文本區存儲程序的機器代碼。
閱讀更多 技匠志 的文章