C++開發(fā)知識點(diǎn)總結(jié)

一、C語言基礎(chǔ)

1、struct 的內(nèi)存對齊和填充問題
其實(shí)只要記住一個(gè)概念和三個(gè)原則就可以了:

  • 一個(gè)概念:
    自然對齊:如果一個(gè)變量的內(nèi)存起始地址正好位于它長度的整數(shù)倍,就被稱做自然對齊。
    如果不自然對齊,會帶來CPU存取數(shù)據(jù)時(shí)的性能損失。(PS:具體應(yīng)該與CPU通過總線讀寫內(nèi)存數(shù)據(jù)的細(xì)節(jié)相關(guān),具體沒有細(xì)究)
  • 三個(gè)原則:
    1)struct 的起始地址需要能夠被其成員中最寬的基本數(shù)據(jù)類型整除;
    2)struct 的size 也必須能夠被其成員中最寬的基本數(shù)據(jù)類型整除;
    3)struct 中每個(gè)成員地址相對于struct 的起始地址的offset,必須是自然對齊的。
    填充的字節(jié)是隨意填充任意字符的,除非賦值前先對結(jié)構(gòu)體變量s進(jìn)行memset(s, 0, sizeof(s));進(jìn)行整個(gè)內(nèi)存清零處理(全0填充,也可以用其他字符全填充)。

2、struct變量能進(jìn)行比較嗎?

  • C語言不能直接用==、>、>=、<、<=這些進(jìn)行比較。但是C++若是對這些用于比較的關(guān)系運(yùn)算符進(jìn)行了重載(重載內(nèi)部可以調(diào)用memcmp函數(shù)),那就可以進(jìn)行比較了。
  • 在對結(jié)構(gòu)體變量都用memset初始化的前提下,可以用memcmp函數(shù)進(jìn)行一個(gè)一個(gè)字節(jié)的比較。int memcmp(const void *buf1, const void *buf2, unsigned int count);相同返回0,大于返回正數(shù),小于返回負(fù)數(shù)。(類似于strcmp函數(shù)的返回值)
  • 若是沒有用memset統(tǒng)一初始化填充字節(jié),那么任意的填充字節(jié)就會影響比較結(jié)果。
  • 結(jié)構(gòu)體變量之間可以直接賦值,如struct student stu1 = stu2;但是注意,賦值是調(diào)用memcpy一個(gè)一個(gè)字節(jié)復(fù)制過去的,而不是按照stu1.id=stu2.id;這樣一個(gè)一個(gè)成員復(fù)制過去的。因此對于用=直接賦值的兩個(gè)變量,再用memcmp比較,肯定是相等的。

3、運(yùn)算符優(yōu)先級問題

  • 優(yōu)先級排序:() > !非(其他單目的) > 算術(shù)運(yùn)算符 > 關(guān)系(> < == !=)運(yùn)算符>邏輯(& ^ | && ||)運(yùn)算符>賦值運(yùn)算符>逗號

https://blog.csdn.net/u013630349/article/details/47444939

4、手寫strcpy字符串拷貝函數(shù)?先問要C風(fēng)格的還是C++風(fēng)格的?

#include <assert.h>
#include <stdio.h>
char* strcpy(char* des, const char* src)  //注意const。C++風(fēng)格的(char& des, const char &src)
{
    assert((des!=NULL) && (src!=NULL));   //注意輸入合法性檢查。
//但是對于C++可以有更好的解決方案,將輸入?yún)?shù)指針改為引用&,就默認(rèn)不能傳入NULL,否則報(bào)錯(cuò)
    char *address = des;          //注意返回值是目的串首地址
    while((*des++ = *src++) != '\0')  
        ;  
    return address;
}

https://www.nowcoder.com/ta/review-c/review?tpId=22&tqId=21053&query=&asc=true&order=&page=4
另有strlen、strcmp、strcat函數(shù)的手寫:https://blog.csdn.net/lisonglisonglisong/article/details/44278013
另外strncpy函數(shù),復(fù)制完后des目標(biāo)字符串的末尾不會有'\0'結(jié)束符,一種解決辦法是自己加上一行 des[n]='\0';。更好的做法是給des字符數(shù)組初始化為全空字符串char des[100]="";或者memset(des, 0, 100);

5、手寫一個(gè)memcpy函數(shù)?

  • 陷阱是要注意有重疊區(qū)的拷貝,比如str[10]="123456789",要memcpy(str+2, str, 5);,若是不考慮重疊區(qū)而簡單的從前往后拷貝,會得到返回"1212189",然而我們想要的結(jié)果是"1234589"
void* Memcpy(void* dest, const void* src, size_t size)
{
    char *pdest, *psrc;
    if (NULL == dest || NULL == src)
    {
        return NULL;
    }
    //要考慮如果目標(biāo)地址和源地址后面有重疊,則要防止源地址后面的原始數(shù)據(jù)被目標(biāo)地址拷貝過來的數(shù)據(jù)覆蓋
    //這種情況就需要從后往前拷貝
    if (dest>src && (char *)dest<(char *)src+size)
    {
        pdest = (char *)dest + size - 1;
        psrc = (char *)src + size - 1;
        while (size--)
        {
            *pdest-- = *psrc--;
        }
    }
    //從前往后正??截愘x值
    else
    {
        pdest = (char*)dest;
        psrc = (char*)src;
        while (size--)
        {
            *pdest++ = *psrc++;
        }
    }
    return dest;
}

6、main函數(shù)的返回值作用?如何捕獲返回值?

  • int main(int argc, char *argv[]) { return 0; }標(biāo)準(zhǔn)main函數(shù)寫法,在命令行運(yùn)行可執(zhí)行文件時(shí),argc表示參數(shù)個(gè)數(shù),argv表示指針數(shù)組,數(shù)組里面每個(gè)元素都是一個(gè)字符串指針首地址。
  • 返回0表示程序正常退出,返回其他數(shù)字的含義由操作系統(tǒng)決定。
  • 在windows中再命令行執(zhí)行上面最簡單的代碼編譯承德可執(zhí)行文件test.exe后,再執(zhí)行命令echo %ERRORLEVEL%(windows命令行大小寫不敏感)可以輸出main函數(shù)的返回值:0,修改為return -1;實(shí)驗(yàn)后也可以看到返回值為:-1。
  • 在linux中可在執(zhí)行可執(zhí)行文件./test后,再輸入一行命令echo $?打印出main函數(shù)的返回值。
  • 在win的test.exe && dir或linux的./test && ls,都可以根據(jù)可執(zhí)行文件的main返回值來判斷是否后面的系統(tǒng)命令是否繼續(xù)執(zhí)行,main返回值為0時(shí)才表示真,則后面的列出當(dāng)前目錄命令都可以執(zhí)行。main返回其他值則后面的不繼續(xù)執(zhí)行。

7、進(jìn)程間通信的方式有哪些?

  • 1、無名管道(父子進(jìn)程兄弟進(jìn)程使用int pipe(int fd[2]);fd[0]單收fd[1]單發(fā));
    2、有名管道FIFO(任意兩進(jìn)程間,是一種文件類型mkfifo("fifo1", 0666);,文件標(biāo)識符fd = open("fifo1", O_WRONLY));管道的實(shí)質(zhì)是一個(gè)內(nèi)核緩沖區(qū),管道一端的進(jìn)程順序地將進(jìn)程數(shù)據(jù)寫入緩沖區(qū),另一端的進(jìn)程則順序地讀取數(shù)據(jù)。
    3、共享內(nèi)存(key = ftok(".", 'z'),創(chuàng)建并獲取共享內(nèi)存號shmid = shmget(key, 1024, IPC_CREAT|0666));采用共享內(nèi)存進(jìn)行通信的一個(gè)主要好處是效率高,因?yàn)檫M(jìn)程可以直接讀寫內(nèi)存,而不需要任何數(shù)據(jù)的拷貝,對于像管道和消息隊(duì)列等通信方式,則需要再內(nèi)核和用戶空間進(jìn)行四次的數(shù)據(jù)拷貝,而共享內(nèi)存則只拷貝兩次:一次從輸入進(jìn)程到共享內(nèi)存區(qū),另一次從共享內(nèi)存到輸出進(jìn)程。
    4、消息隊(duì)列(需要內(nèi)核維護(hù)這個(gè)消息隊(duì)列key = ftok("/etc/passwd",'z'),隊(duì)列號msqid = msgget(key, IPC_CREAT|0777));消息隊(duì)列克服了信號傳遞信息少、管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺點(diǎn)。消息隊(duì)列,就是一個(gè)消息的鏈表,是一系列保存在內(nèi)核中消息的列表。用戶進(jìn)程通過獲取消息隊(duì)列的唯一ID可以向消息隊(duì)列添加消息,也可以向消息隊(duì)列讀取消息。
    5、信號(如一些引發(fā)中斷信號kill);幾十種信號和以他們的名稱命名的常量,通常程序中直接包含<signal.h>就好。信號是在軟件層次上對中斷機(jī)制的一種模擬,是一種異步通信方式,信號常用于用戶進(jìn)程和內(nèi)核之間直接交互。內(nèi)核也可以利用信號來通知用戶空間的進(jìn)程來通知用戶空間發(fā)生了哪些系統(tǒng)事件。信號事件有兩個(gè)來源:1)硬件來源,例如按下了cltr+C,通常產(chǎn)生中斷信號sigint。2)軟件來源,例如使用系統(tǒng)調(diào)用或者命令發(fā)出信號。最常用的發(fā)送信號的系統(tǒng)函數(shù)是kill,raise函數(shù)。軟件來源還包括一些非法運(yùn)算等操作。
    6、信號量(也是需要由內(nèi)核維護(hù)P獲取信號量操作減一,V釋放信號量操作加1,只是同步計(jì)數(shù)器不傳數(shù)據(jù)key = ftok(".", 'z'),獲取信號量idsem_id = semget(key, 1, IPC_CREAT|0666));
    7、socket(可跨網(wǎng)絡(luò)主機(jī)進(jìn)程通信)sys/socket.h 或 winsock2.h
    http://www.itdecent.cn/p/21fba1542026
  • socket網(wǎng)絡(luò)跨主機(jī)的進(jìn)程間通信是以五元組【源IP,源端口,目的IP,目的端口,TCP或UDP協(xié)議】,來找到不同的進(jìn)程,并互發(fā)數(shù)據(jù)進(jìn)行通信。
  • 同一主機(jī)上的進(jìn)程間通信,大都是使用在本主機(jī)上唯一的key關(guān)鍵值來標(biāo)識一個(gè)可供不同進(jìn)程訪問的資源,這個(gè)唯一的關(guān)鍵值又大都是與某一個(gè)實(shí)際存在的文件綁定,利用ftok()函數(shù)key_t ftok( const char * fname, int id );,可根據(jù)fname即某文件名(一般是系統(tǒng)上一定存在的文件)找到它的索引節(jié)點(diǎn)號(唯一標(biāo)識一個(gè)文件的node_id)搭配提供的另一個(gè)參數(shù)id得到唯一的key值=(16進(jìn)制的)id連接node_id。因此不同的進(jìn)程只要都遵守這個(gè)約定提供相同的參數(shù)給ftok()函數(shù)就都可以獲取到這個(gè)唯一的key_id,然后根據(jù)key_id再獲取到對應(yīng)的進(jìn)程間通信方式的id號,然后對其進(jìn)行讀寫數(shù)據(jù)操作。
    類似的IPC有FIFO命名管道、消息隊(duì)列(前面兩個(gè)自帶進(jìn)程同步效果),信號量(只能控制進(jìn)程同步,要想互發(fā)數(shù)據(jù)需要結(jié)合共享內(nèi)存)、共享內(nèi)存
  • 無名管道由于只在父子進(jìn)程或兄弟進(jìn)程間通信,可以在主進(jìn)程中通過pipe(fd)隨機(jī)產(chǎn)生出管道的文件描述符。不需要在主機(jī)上唯一,因?yàn)橹辉谶@幾個(gè)相關(guān)的進(jìn)程中私有。

8、線程間的通信

  • 共享的全局變量,然后利用全局的鎖變量或者信號量來實(shí)現(xiàn)同步互斥訪問共享的全局變量達(dá)到通信的目的。
  • 消息傳遞(如窗口應(yīng)用的點(diǎn)擊按鈕事件傳遞)、事件訂閱機(jī)制等

9、進(jìn)程調(diào)度算法有哪些?Linux使用的什么進(jìn)程調(diào)度方法?

  • 【解】進(jìn)程調(diào)度算法有下面幾種:
    先來先服務(wù)
    短作業(yè)優(yōu)先
    時(shí)間片輪轉(zhuǎn)
    基于優(yōu)先級

Linux系統(tǒng)中,進(jìn)程分為實(shí)時(shí)和非實(shí)時(shí)兩種:

  • 實(shí)時(shí)進(jìn)程(相對于普通進(jìn)程優(yōu)先級更高)
    SCHED_FIFO —> 相同優(yōu)先級時(shí),先來先服務(wù);不同優(yōu)先級,搶占式調(diào)度。進(jìn)程一旦占用cpu則一直運(yùn)行,直到有更高優(yōu)先級任務(wù)到達(dá)或自己放棄。
    SCHED_RR —> 時(shí)間片輪轉(zhuǎn),搶占式調(diào)度。
  • 普通進(jìn)程
    SCHED_OTHER —> priority(靜態(tài)優(yōu)先級)+counter(剩余時(shí)間片)之和作為動態(tài)優(yōu)先級,基于動態(tài)優(yōu)先級的時(shí)間片輪轉(zhuǎn)。

10、虛擬內(nèi)存和物理內(nèi)存?

  • 先說說為什么會有虛擬內(nèi)存和物理內(nèi)存的區(qū)別。正在運(yùn)行的一個(gè)進(jìn)程,他所需的內(nèi)存是有可能大于內(nèi)存條容量之和的,比如你的內(nèi)存條是256M,你的程序卻要?jiǎng)?chuàng)建一個(gè)2G的數(shù)據(jù)區(qū),那么不是所有數(shù)據(jù)都能一起加載到內(nèi)存(物理內(nèi)存)中,勢必有一部分?jǐn)?shù)據(jù)要放到其他介質(zhì)中(比如硬盤),待進(jìn)程需要訪問那部分?jǐn)?shù)據(jù)時(shí),再通過調(diào)度從磁盤進(jìn)入物理內(nèi)存。所以,虛擬內(nèi)存是進(jìn)程運(yùn)行時(shí)所有內(nèi)存空間的總和,并且可能有一部分不在物理內(nèi)存中而在磁盤中,而物理內(nèi)存就是我們平時(shí)所了解的內(nèi)存條。有的地方呢,也叫這個(gè)虛擬內(nèi)存為內(nèi)存交換區(qū)。

1)每個(gè)進(jìn)程都有自己的虛擬內(nèi)存空間,所有進(jìn)程共享物理內(nèi)存空間。
2)虛擬內(nèi)存大小和機(jī)器的位數(shù)有關(guān),如32位的是4GB的地址總線(0~0xFFFFFFFF字節(jié))其中每個(gè)進(jìn)程都有自己的0~0xBFFFFFFF的3GB虛擬內(nèi)存空間——用戶空間,然后所有進(jìn)程共用0xCFFFFFFF~0xFFFFFFFF這1GB的虛擬內(nèi)存空間——內(nèi)核空間。64位的有2^64字節(jié)的地址總線。


3)該圖顯示了兩個(gè) 64 位進(jìn)程的虛擬地址空間:Notepad.exe 和 MyApp.exe。每個(gè)進(jìn)程都有其各自的虛擬地址空間,范圍從 0x000'0000000 至 0x7FF'FFFFFFFF(8TB用戶進(jìn)程虛擬內(nèi)存空間)。每個(gè)陰影框都表示虛擬內(nèi)存或物理內(nèi)存的一個(gè)頁面(大小都為4KB)。注意,Notepad 進(jìn)程使用從 0x7F7'93950000 開始的虛擬地址的三個(gè)相鄰頁面。但虛擬地址的這三個(gè)相鄰頁面會映射到物理內(nèi)存中的非相鄰頁面。而且還注意,兩個(gè)進(jìn)程都使用從 0x7F7'93950000 開始的虛擬內(nèi)存頁面,但這些虛擬頁面都映射到物理內(nèi)存的不同頁面,說明個(gè)進(jìn)程間的虛擬內(nèi)存互不影響。
4)這種虛擬內(nèi)存一個(gè)頁page(4KB)和物理內(nèi)存一個(gè)頁幀(4KB)之間的映射關(guān)系,由操作系統(tǒng)的一個(gè)頁表來維護(hù),頁表中給各個(gè)頁都有編號(頁號和頁幀號對應(yīng)映射)。進(jìn)程中虛擬內(nèi)存頁》頁表中的頁號—MMU內(nèi)存管理單元—頁表中的頁幀號《物理內(nèi)存中的頁幀【由MMU的頁表來維護(hù)映射關(guān)系】

5)但是問題來了,虛擬內(nèi)存頁的個(gè)數(shù)=3GB/4KB > 物理內(nèi)存頁幀的個(gè)數(shù)=256MB/4KB,豈不是有些虛擬內(nèi)存頁的地址永遠(yuǎn)沒有對應(yīng)的物理內(nèi)存地址空間?不是的,操作系統(tǒng)是這樣處理的。操作系統(tǒng)有個(gè)缺頁中斷(page fault)缺頁異常功能。操作系統(tǒng)把暫時(shí)不訪問的物理內(nèi)存頁幀,讓他缺頁失效,并把它的原內(nèi)容寫入磁盤轉(zhuǎn)存起來——這個(gè)過程叫分頁-分頁是磁盤和內(nèi)存間傳輸數(shù)據(jù)塊的最小單位。,隨后把當(dāng)前需要訪問的頁放到頁幀中,并修改頁表中的映射,這樣就保證所有的頁都有被調(diào)度的可能了。當(dāng)進(jìn)程又要訪問原轉(zhuǎn)存的內(nèi)容,會先訪問原物理內(nèi)存頁但是引發(fā)缺頁中斷然后從磁盤中取出頁內(nèi)容到物理內(nèi)存頁,然后進(jìn)程訪問到數(shù)據(jù)繼續(xù)執(zhí)行。這就是處理虛擬內(nèi)存地址到物理內(nèi)存的步驟。

https://www.cnblogs.com/curtful/archive/2012/02/16/2354496.html

11、為什么需要虛擬內(nèi)存?通過虛擬地址訪問內(nèi)存有哪些優(yōu)勢?

  • 虛擬內(nèi)存地址可以提供只讀只寫的訪問權(quán)限來保護(hù)實(shí)際對應(yīng)的物理地址的數(shù)據(jù)。
  • 程序可以使用一系列相鄰的虛擬地址來訪問物理內(nèi)存中不相鄰的大內(nèi)存緩沖區(qū)。
  • 不同進(jìn)程使用的虛擬地址彼此隔離。一個(gè)進(jìn)程中的代碼無法更改正在由另一進(jìn)程或操作系統(tǒng)使用的物理內(nèi)存。
  • 程序可以使用一系列虛擬地址來訪問大于可用物理內(nèi)存的內(nèi)存緩沖區(qū)。當(dāng)物理內(nèi)存的供應(yīng)量變小時(shí),內(nèi)存管理器會將(暫時(shí)不用的)物理內(nèi)存頁(通常大小為 4 KB)保存到磁盤文件,從而空出可用的物理內(nèi)存。數(shù)據(jù)或代碼頁會根據(jù)需要在物理內(nèi)存與磁盤之間移動。

12、死鎖產(chǎn)生的四個(gè)條件,死鎖發(fā)生后怎么檢測和恢復(fù)?

  • 死鎖的四個(gè)必要條件:
    互斥條件:一個(gè)資源每次只能被一個(gè)進(jìn)程使用。
    請求與保持條件:一個(gè)進(jìn)程在申請新的資源的同時(shí)保持對原有資源的占有。
    不剝奪條件:進(jìn)程已獲得的資源,在未使用完之前,不能強(qiáng)行剝奪。
    循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
  • 死鎖發(fā)生后:
    檢測死鎖:首先為每個(gè)進(jìn)程和每個(gè)資源指定一個(gè)唯一的號碼,然后建立資源分配表和進(jìn)程等待表。
    解除死鎖:當(dāng)發(fā)現(xiàn)有進(jìn)程死鎖后,可以直接撤消死鎖的進(jìn)程或撤消代價(jià)最小的進(jìn)程,直至有足夠的資源可用,死鎖狀態(tài)消除為止。

13、不用中間變量實(shí)現(xiàn)兩個(gè)元素交換值。

void swap1(int &a, int &b)
{  //異或操作只適用于整型數(shù),不能用于小數(shù)
    if (a != b)  //如果兩個(gè)數(shù)地址相同,異或操作會清零
    {
        a ^= b;
        b ^= a;
        a ^= b;
    }
}
void swap2(double  &a, double &b)
{//用double型可以使適用的范圍更大,防止加法和越界
    a = a + b;
    b = a - b;
    a = a - b;
}

14、一些常見的位操作

  • 位操作符的運(yùn)算優(yōu)先級比加減更低,如int a = 1 << 2+ 1;得到a的值1左移3位得8,與int a = (1 << 2)+ 1;得5不同。
  • 取得a的相反數(shù)-a:~a+1; a取反再加一。
  • 可以用if ((a & 1) == 0)代替if (a % 2 == 0)來判斷a是不是偶數(shù)。即只需判斷a的最低位是0還是1。
  • 取得絕對值
int my_abs(int a)
{
    int i = a >> 31;  //得到最高位符號位,a為正或0返回0,a為負(fù)數(shù)返回-1
    return i == 0 ? a : (~a + 1);  //正數(shù)返回本身,負(fù)數(shù)返回相反數(shù)
    //return (a^i)-i;  //a^0 ==a本身;a^(-1) == a取反,再減-1即加1得到相反數(shù)
}
  • 給出一個(gè)16位的無符號整數(shù)。稱這個(gè)二進(jìn)制數(shù)的前8位為“高位”,后8位為“低位”?,F(xiàn)在寫一程序?qū)⑺母叩臀唤粨Q。a = (a >> 8) | (a << 8);

15、守護(hù)、僵尸、孤兒進(jìn)程的概念?

  • 守護(hù)進(jìn)程:運(yùn)行在后臺的一種特殊進(jìn)程,獨(dú)立于控制終端并周期性地執(zhí)行某些任務(wù)。如何實(shí)現(xiàn)守護(hù)進(jìn)程?(孤兒進(jìn)程)
    僵尸進(jìn)程:一個(gè)進(jìn)程 fork 子進(jìn)程,子進(jìn)程退出,而父進(jìn)程沒有 wait/waitpid子進(jìn)程,那么子進(jìn)程的進(jìn)程描述符仍保存在系統(tǒng)中,這樣的進(jìn)程稱為僵尸進(jìn)程。
    孤兒進(jìn)程:一個(gè)父進(jìn)程退出,而它的一個(gè)或多個(gè)子進(jìn)程還在運(yùn)行,這些子進(jìn)程稱為孤兒進(jìn)程。(孤兒進(jìn)程將由 init 進(jìn)程(進(jìn)程號pid為1)收養(yǎng)并對它們完成狀態(tài)收集工作)

16、一個(gè)進(jìn)程的主線程結(jié)束后,其他線程怎么辦?

  • 通常一個(gè)進(jìn)程的主線程是指的main()函數(shù)啟動后的那個(gè)線程。實(shí)際上一個(gè)進(jìn)程的所有線程是平等的,沒有主線程子線程之分。
  • 如果讓main()函數(shù)執(zhí)行到最后一行(默認(rèn)return 0;)或return其他值來結(jié)束主線程,然后調(diào)用glibc庫函數(shù)exit,exit進(jìn)行相關(guān)清理工作后調(diào)用_exit系統(tǒng)調(diào)用退出該進(jìn)程。因?yàn)檫M(jìn)程結(jié)束了,所以該進(jìn)程所有的線程也就結(jié)束了。
  • 如果main()主線程在沒有運(yùn)行到return退出前就由pthread_cancel(tid);終止了,那么就不會引發(fā)進(jìn)程結(jié)束,其他的線程可以繼續(xù)正常運(yùn)行。而所謂的線程之間共享的全局變量是在main()函數(shù)之外也就是主線程之外的,因此主線程main函數(shù)內(nèi)的局部變量銷毀對其他線程也無影響。
  • 如果系統(tǒng)調(diào)用#kill 某線程號 ,也是可以結(jié)束整個(gè)進(jìn)程的。

17、內(nèi)存泄漏如何避免?發(fā)生后如何檢查定位?

  • 內(nèi)存泄漏是指用malloc/realloc/new申請的堆內(nèi)存,在銷毀指向這些堆內(nèi)存的指針之前沒有調(diào)用對應(yīng)的free/delete歸還內(nèi)存給堆。使得這些堆內(nèi)存不能再被利用。然而不管內(nèi)存泄漏多么輕微,當(dāng)程序長時(shí)間運(yùn)行時(shí),其破壞力是驚人的 - 從性能下降到內(nèi)存耗盡,甚至?xí)绊懫渌绦虻恼_\(yùn)行。
  • 內(nèi)存泄漏的避免:一是從編程習(xí)慣來談要求我們記得在同一個(gè)代碼塊成對使用malloc/free或new/delete或new[]數(shù)組/delete[]數(shù)組,最好不要在函數(shù)內(nèi)申請內(nèi)存然后返回指針,這樣在調(diào)用函數(shù)后很容易忘記這個(gè)指針指向的內(nèi)存是堆內(nèi)存而忘記手動釋放;而是應(yīng)該在函數(shù)外申請內(nèi)存,然后將指針作為參數(shù)傳遞給函數(shù)使用,這樣在函數(shù)使用完后我們還可以從上面代碼看到這個(gè)指針是指向堆內(nèi)存的而輕松手動釋放。二是在C++中可以使用智能指針,使得在指向堆內(nèi)存的指針在用完后會隨著智能指針的析構(gòu)而調(diào)用free/delete函數(shù)釋放該指針指向的內(nèi)存,就不需要手動顯示地去釋放。三是使用內(nèi)存池去集中申請堆內(nèi)存,然后集中釋放堆內(nèi)存。四是別忘了將基類的析構(gòu)函數(shù)定義為虛函數(shù),否則在多態(tài)使用父類指針指向子類對象時(shí),析構(gòu)的時(shí)候會無法調(diào)用子類的析構(gòu)函數(shù)釋放子類的資源,從而造成泄漏。
  • 內(nèi)存泄漏發(fā)生后的定位:一是先找到發(fā)生內(nèi)存泄漏的程序在linux下可以用top命令列出正在運(yùn)行的程序占用的內(nèi)存,windows下可以查看任務(wù)管理器,從中分析出異常的程序,然后通過#ps aux| grep 程序名》得到進(jìn)程號pid》然后#kill pid結(jié)束這個(gè)進(jìn)程;二是尋找程序中引發(fā)內(nèi)存泄漏的代碼,若是代碼量不大,可以通過人工分析是否malloc/new對應(yīng)的指針都得到了正確的釋放,然后重點(diǎn)檢查程序會循環(huán)執(zhí)行的那些代碼就是引起內(nèi)存泄漏一直增長的地方。如果還是找不到,那就需要借助一些工具來定位,比如使用vs編程可以安裝一個(gè)VLD(Visual Leak Detector)插件,使用的時(shí)候#include <vld.h>就可以了調(diào)試程序看到代碼檢查的報(bào)錯(cuò)結(jié)果。在linux下可以使用 Valgrind Memcheck工具的使用方式如下:valgrind --tool=memcheck ./a.out其中a.out為要檢查的程序編譯生成的可執(zhí)行文件(為了使valgrind發(fā)現(xiàn)的錯(cuò)誤更精確,能夠定位到源代碼行,建議在編譯時(shí)加上-g參數(shù)即加入gdb調(diào)試信息)。

二、C++基礎(chǔ)

1、new表達(dá)式(new operator)和std::operator new標(biāo)準(zhǔn)庫函數(shù)的區(qū)別

  • new operator 是先調(diào)用operator new函數(shù)來分配返回值為void*的內(nèi)存,然后再調(diào)用作用類型的構(gòu)造函數(shù)去初始化賦值這塊內(nèi)存。如string * str = new string("hello");就是對new表達(dá)式的常用方式。

這里我們可以這么理解,new表達(dá)式(new operator)其實(shí)可以分解為兩部,即先調(diào)用new操作符(operator new)申請內(nèi)存,再調(diào)用placement new來初始化對象。即上面對new表達(dá)式的使用string * str = new string("hello");等價(jià)于下面兩句。

void *buffer = ::operator new(sizeof(string));
buffer = new(buffer) string(“hello”);
  • 而operator new函數(shù)相當(dāng)于是malloc,其實(shí)它的內(nèi)部實(shí)現(xiàn)也就是調(diào)用了void * malloc(size_t size);函數(shù)。
 void* operator new(size_t sizt)
{
      return malloc(size);      
}

詳見:http://www.itdecent.cn/p/a07ba8f384da

2、new和malloc有哪些區(qū)別?


最重要的是要說到最下面一條。
詳見:https://www.cnblogs.com/QG-whz/p/5140930.html

3、頭文件防衛(wèi)式編程,防止頭文件多次嵌套導(dǎo)入。
比如要編寫頭文件mystring.h,就可以這樣寫。如果統(tǒng)一遵循這種規(guī)則,那么就不會出現(xiàn)mystring.h頭文件內(nèi)容在編譯前預(yù)處理時(shí)被重復(fù)導(dǎo)入的情況。

#ifndef __MYSTRING__
#define __MYSTRING__

#include<string.h>    //用到C語言的字符串處理函數(shù)
class mystring
{
public:
        mystring();
        ~mystring();
private:
        char * head;
        int length;
};
#endif

4、const函數(shù)?——函數(shù)定義后面加const有什么作用?

  • 只有類成員函數(shù)后面才能加const,普通函數(shù)沒有所謂的const函數(shù),不能在后面加const。如復(fù)數(shù)complex類的獲取實(shí)部成員函數(shù)int getReal() const { return real; }
  • 作用是表示此類成員函數(shù)不能改變所有類成員變量的值。
  • 當(dāng)定義一個(gè)const的類對象時(shí)const complex con1;,此對象con1就只能調(diào)用這些const成員函數(shù),調(diào)用非const成員函數(shù)就會報(bào)錯(cuò)。

5、C++內(nèi)存分區(qū)?進(jìn)程虛擬內(nèi)存分布?共有5個(gè)分區(qū)

6、一個(gè)進(jìn)程可用的內(nèi)存大小、線程可用內(nèi)存大?。慷褍?nèi)存多大?棧內(nèi)存多大?

  • 根據(jù)上面這張圖可以分析出,一個(gè)用戶進(jìn)程最多可用3GB內(nèi)存,進(jìn)程里既有堆又有棧。而一個(gè)線程只有自己的??臻g,所以可用的最大棧內(nèi)存也就是上圖說的8MB或10MB(可手動修改)。因此一個(gè)最多可開辟3GB/10MB=300個(gè)線程。
  • 對于4GB內(nèi)存封頂?shù)?2位系統(tǒng),用戶空間堆內(nèi)存<3GB(留1GB給內(nèi)核空間),而棧內(nèi)存一般只有8M(ubuntu)或10M(centos)各系統(tǒng)默認(rèn)值不同,可修改。

7、用vs編程時(shí)debug和release版本有啥區(qū)別?

  • Debug通常稱為調(diào)試版本,它包含很多調(diào)試信息,并且不作任何優(yōu)化,便于程序員調(diào)試程序(加斷點(diǎn)實(shí)時(shí)查看變量值)。會檢查assert斷言語句。也可關(guān)閉assert斷言——先#define NDEBUG#include<assert.h>,就可以將后面寫的那些assert斷言語句關(guān)閉檢查。
  • Release稱為發(fā)布版本,它往往是進(jìn)行了各種優(yōu)化(比如調(diào)整某些變量在棧內(nèi)存的地址順序),使得程序在代碼大小和運(yùn)行速度上都是最優(yōu)的,以便用戶很好的使用。會忽略assert斷言語句。

8、面向?qū)ο蟮乃拇筇匦灾v一下?

  • 抽象:將多種事物抽象出共同特征形成一個(gè)抽象父類。
  • 封裝:將變量和對變量的操作組合在一起形成一個(gè)類或者一個(gè)模塊,就是封裝。一處封裝,多處調(diào)用。目的是代碼復(fù)用。
  • 繼承:子類在原有父類代碼的基礎(chǔ)上進(jìn)行擴(kuò)展。目的是代碼復(fù)用。
  • 多態(tài):調(diào)用同名函數(shù),卻因?yàn)樯舷挛沫h(huán)境不同,而會有不同的實(shí)現(xiàn)。一個(gè)函數(shù)接口多種實(shí)現(xiàn)。目的是接口復(fù)用。

9、C++多態(tài)分幾類?多態(tài)有幾種實(shí)現(xiàn)方式?

10、C能直接用C++寫的動態(tài)庫嗎?C++能直接用C寫的動態(tài)庫嗎?

  • 都不能直接互用。主要原因是C++有函數(shù)重載,相同函數(shù)在匯編代碼中的會帶上參數(shù)表示,而C不支持函數(shù)重載因此在匯編中函數(shù)名只有函數(shù)名。比如int add(int a, int b);,在C++編譯后的匯編代碼中是_add_int_int或其他表示,而在C語言匯編代碼中只是_add
  • 因此,C++要調(diào)用用C的動態(tài)庫,不用重新編譯C,只需在自己的C++代碼中吧要調(diào)用的C函數(shù)原型用extern "C" int add(int a, int b);,這樣該C++代碼在編譯是就會將這個(gè)add函數(shù)原型用C的形式編譯成_add。然后就和原C的動態(tài)庫函數(shù)名對上了。否則C++代碼中編譯成_add_int_int在調(diào)用C動態(tài)庫時(shí)會報(bào)錯(cuò)找不到該函數(shù)。
  • C要調(diào)用C++的動態(tài)庫時(shí),必須修改原C++代碼,將要被C調(diào)用的函數(shù)原型修改為extern "C" int add(int a, int b);,就可以被編譯成C形式了,然后重新編譯就可供C調(diào)用。編譯完成后,提供給c使用的頭文件里面不能包含extern “C”,可以使用宏開關(guān)解決,也可以重新寫個(gè)頭文件。當(dāng)然還有一點(diǎn)就是該函數(shù)內(nèi)部不能包含用C++特有的東西。
  • 總結(jié),要想互相能調(diào)用,都只需要對C++代碼進(jìn)行修改,函數(shù)原型前加extern "C",而C代碼不需要修改。

11、從源代碼到可執(zhí)行文件經(jīng)歷了那幾步?

  • 文本源代碼》預(yù)處理(得去注釋、宏替換、展開頭文件等的文本源碼》編譯(得文本匯編代碼.S)》匯編(得二進(jìn)制的.o目標(biāo)文件)》鏈接多個(gè).o(得到二進(jìn)制可執(zhí)行文件)

12、C和C++的區(qū)別

  • C語言更加注重的是過程,C++除了保留C語言的優(yōu)點(diǎn),還增加了面向?qū)ο蟮臋C(jī)制。
  • 應(yīng)用場景來看,由于C沒有面向?qū)ο?,所以在程序?guī)模太大的時(shí)候?qū)懫饋砗軓?fù)雜,面向?qū)ο蟮腃++更適合大規(guī)模的程序。
  • C沒類class面向?qū)ο蟾嗟氖敲嫦蜻^程編程,struct的用法也有差異,比如定義struct變量的時(shí)候C要帶上struct關(guān)鍵字,而C++的struct用法和class一樣,只是內(nèi)部數(shù)據(jù)訪問權(quán)限struct默認(rèn)public,calss默認(rèn)private。
  • C沒有bool型,可用int型代替。C沒有方便的string可用
  • C++的泛型編程和STL提供的多種容器也是C所沒有的。
  • 函數(shù)重載,操作符重載,C也沒有。這也導(dǎo)致兩者代碼不能直接調(diào)用。

13、C++和java的區(qū)別

  • 我覺得C++與Java最大的區(qū)別是在于內(nèi)存管理上,C++的內(nèi)存管理是需要程序員自己控制的,自己開了需要自己去釋放。然而Java提供了JVM,JVM就是用來進(jìn)行內(nèi)存管理的,不需要程序員自己手動開關(guān)。
  • Java呢,摒棄了C++很多復(fù)雜的特性,比如指針、多繼承、操作符重載等等,相對來說Java的編程學(xué)習(xí)入門比較容易。

14、static_cast 和 dynamic_cast 的區(qū)別?兩個(gè)操作符

  • cast轉(zhuǎn)換發(fā)生的時(shí)間不同,一個(gè)是static靜態(tài)編譯時(shí),一個(gè)是動態(tài)運(yùn)行時(shí);
  • static_cast是相當(dāng)于C的強(qiáng)制類型轉(zhuǎn)換,用起來可能有一點(diǎn)危險(xiǎn),不提供運(yùn)行時(shí)的檢查來確保轉(zhuǎn)換的安全性。
  • dynamic_cast用于轉(zhuǎn)換指針和和引用,不能用來轉(zhuǎn)換對象 —— 主要用于類層次間的上行轉(zhuǎn)換和下行轉(zhuǎn)換,還可以用于類之間的交叉轉(zhuǎn)換。在類層次間進(jìn)行上行轉(zhuǎn)換時(shí),dynamic_cast和static_cast的效果是一樣的;在進(jìn)行下行轉(zhuǎn)換時(shí),dynamic_cast具有類型檢查的功能,比static_cast更安全。在多態(tài)類型之間的轉(zhuǎn)換主要使用dynamic_cast,因?yàn)轭愋吞峁┝诉\(yùn)行時(shí)信息。

15、說幾個(gè)C++11的新特性。

1)auto類型推導(dǎo):auto vData = vectors.begin();自動推導(dǎo)出vector中的數(shù)據(jù)類型
2)范圍for循環(huán):for( auto v:vectors) { cout<<v<<endl; }這樣的v是只讀的,也可以用到&引用,作為可修改的for(auto &v:vectors){ v=v+1; }
3)lambda表達(dá)式:是一個(gè)匿名仿函數(shù)(函數(shù)對象),一定是[]中括號開頭。[可選的捕捉到的外部變量] (可選的形參傳遞)mutable可選 throwSpec可選 -> retType可選 { ... },當(dāng)中括號和大括號之間有東西時(shí),也一定要有小括號。

4)override 和 final 關(guān)鍵字:兩個(gè)繼承控制關(guān)鍵字,override明確地表示一個(gè)函數(shù)是對基類中一個(gè)虛函數(shù)的重寫,virtual void func(int) override;確保在派生類中聲明的重載函數(shù)跟基類的虛函數(shù)有相同的簽名。final阻止類的進(jìn)一步派生class TaskManager {/*..*/} final;和虛函數(shù)的進(jìn)一步重載virtual void func(int) override final;。
5)空指針常量nullptr。C語言中NULL其實(shí)是define成0,所以兩者等價(jià),但是因?yàn)镃++支持函數(shù)重載,那么在遇到函數(shù)f(int)f(void*)時(shí),f(0)和f(NULL)就會產(chǎn)生歧義不明確指向的是哪個(gè),而有了f(nullptr)后就明確了是指向f(void*)。
6)智能指針(模板類)shared_ptr(附帶weak_ptr)、unique_ptr。shared_ptr采用引用計(jì)數(shù)的方式管理所指向的對象。當(dāng)有一個(gè)新的shared_ptr指向同一個(gè)對象時(shí)(復(fù)制shared_ptr等),引用計(jì)數(shù)加1。當(dāng)shared_ptr離開作用域時(shí),引用計(jì)數(shù)減1。當(dāng)引用計(jì)數(shù)為0時(shí),釋放所管理的內(nèi)存。weak_ptr一般和shared_ptr配合使用。它可以指向shared_ptr所指向的對象,但是卻不增加對象的引用計(jì)數(shù)。這樣就有可能出現(xiàn)weak_ptr所指向的對象實(shí)際上已經(jīng)被釋放了的情況。因此,weak_ptr有一個(gè)lock函數(shù),嘗試取回一個(gè)指向?qū)ο蟮膕hared_ptr。unique_ptr對于所指向的對象,正如其名字所示,是獨(dú)占的。所以,不可以對unique_ptr進(jìn)行拷貝、賦值等操作,但是可以通過release函數(shù)在unique_ptr之間轉(zhuǎn)移控制權(quán)。上述對于拷貝的限制,有兩個(gè)特殊情況,即unique_ptr可以作為函數(shù)的返回值和參數(shù)使用,這時(shí)雖然也有隱含的拷貝存在,但是并非不可行的。
1、所有的智能指針類都有一個(gè)explicit構(gòu)造函數(shù),以普通指針作為參數(shù)。因此不能自動將普通指針轉(zhuǎn)換為智能指針對象,必須顯式調(diào)用構(gòu)造函數(shù)。 2、所有智能指針都是對原始指針執(zhí)行淺拷貝,即所有指針指向同一塊內(nèi)存,而不是深拷貝創(chuàng)建多個(gè)備份。 3、當(dāng)一個(gè)shared_ptr對象sp2是由sp1拷貝構(gòu)造或者賦值構(gòu)造得來的時(shí)候,實(shí)際上構(gòu)造完成后sp1內(nèi)部的__shared_count對象包含的指向管理對象的指針與sp2內(nèi)部的__shared_count對象包含的指向管理對象的指針是相等的,也就是說當(dāng)多個(gè)shared_ptr對象來管理同一個(gè)對象時(shí),它們共同使用同一個(gè)動態(tài)分配的管理對象指針。 4、都是運(yùn)用了RAII技術(shù)來實(shí)現(xiàn)的。原生指針被轉(zhuǎn)為智能指針類使用之后,就不要再使用原生指針,否則容易出錯(cuò)。即不要把一個(gè)原生指針給多個(gè)shared_ptr管理。 5、引用頭文件#include<memory>。智能指針是模板類而不是指針。 6、智能指針實(shí)質(zhì)就是重載了->和*操作符的類,由類來實(shí)現(xiàn)對內(nèi)存的管理,確保即使有異常產(chǎn)生,也可以通過智能指針類的析構(gòu)函數(shù)完成內(nèi)存的釋放。
7)initializer_list<>初始化列表,用于給變量定義時(shí)用{}賦初值,如vector<int> v1{2,3,5};或者是直接用于函數(shù)的參數(shù)列表如max({5,2,8,1})得到8。這種對應(yīng)于{}大括號的初始化列表,底層是一個(gè)array容器,利用這個(gè)array先把初值開辟內(nèi)存,然后再調(diào)用對應(yīng)的接受initializer_list<>形參的構(gòu)造函數(shù),然后進(jìn)行數(shù)據(jù)的淺拷貝。
8)explicit關(guān)鍵字,多用于構(gòu)造函數(shù)前,明確表示該構(gòu)造函數(shù)必須被顯示的調(diào)用,而不能被用于隱式轉(zhuǎn)換調(diào)用(如多個(gè)參數(shù)中后面的參數(shù)有默認(rèn)值時(shí),只給出前面參數(shù)一般也會觸發(fā)隱式轉(zhuǎn)換構(gòu)造),現(xiàn)在加了explicit關(guān)鍵字就是為了禁止這種隱式的轉(zhuǎn)換。
9)decltype關(guān)鍵字,用于從操作的對象或表達(dá)式中得到類型,如decltype(lambda)表示得到一個(gè)lambda對象的類型。
用途一:用于指明模板函數(shù)的返回類型。


用途二:用于傳遞lambda表達(dá)式對象的類型。

10)move移動語義,對應(yīng)于右值引用,可以將右值(只能放在賦值等號=右邊的變量,std::move(非右值)可以強(qiáng)制轉(zhuǎn)變?yōu)橛抑担┑馁Y源(內(nèi)存空間和值)竊取再直接賦給左值,使得左值不用再定義和開辟額外的內(nèi)存,加快move拷貝的速度。常用于在構(gòu)造函數(shù)的時(shí)候新增一個(gè)類似于拷貝構(gòu)造(string(const string &str){ ... })的move構(gòu)造(string(string &&str){ ... })。本質(zhì)上是淺拷貝的行為,即拷貝的時(shí)候只是將原指針的值(變量內(nèi)存地址)賦給新的指針,然后原指針賦NULL不再使用,實(shí)際的那塊內(nèi)存沒有動。相對的深拷貝就是對內(nèi)存中每一個(gè)數(shù)據(jù)都重新拷貝到了一塊新的內(nèi)存。
move移動語義右值引用.jpg

https://blog.csdn.net/FX677588/article/details/70157088
https://www.cnblogs.com/guxuanqing/p/6707824.html

16、如何在一個(gè)不安全的環(huán)境中實(shí)現(xiàn)安全的數(shù)據(jù)通信?

  • 要實(shí)現(xiàn)數(shù)據(jù)的安全傳輸,當(dāng)然就要對數(shù)據(jù)進(jìn)行加密了。
  • 如果使用對稱加密算法,加解密使用同一個(gè)密鑰,除了自己保存外,對方也要知道這個(gè)密鑰,才能對數(shù)據(jù)進(jìn)行解密。如果你把密鑰也一起傳過去,就存在密碼泄漏的可能。所以我們使用非對稱加密算法,過程如下:

首先 數(shù)據(jù)接收方 生成一對密鑰,即私鑰和公鑰;
然后,數(shù)據(jù)接收方 將公鑰發(fā)送給 數(shù)據(jù)發(fā)送方;
數(shù)據(jù)發(fā)送方用收到的公鑰對數(shù)據(jù)加密,再發(fā)送給接收方;
接收方收到數(shù)據(jù)后,使用自己的私鑰解密。

》由于在非對稱算法中,公鑰加密的數(shù)據(jù)必須用對應(yīng)的私鑰才能解密,而私鑰又只有接收方自己知道,這樣就保證了數(shù)據(jù)傳輸?shù)陌踩浴?/p>

17、面向?qū)ο笏枷胫饕绾误w現(xiàn)?

  • 面向?qū)ο蟮娜筇匦允欠庋b、繼承、多態(tài)。
  • 實(shí)際中,對一個(gè)類的數(shù)據(jù)成員和方法成員進(jìn)行封裝,對抽象基類的繼承可以很好的復(fù)用基類封裝的數(shù)據(jù)和方法成員,多態(tài)的應(yīng)用可以使父類指針指向子類的實(shí)例對象從而在程序運(yùn)行時(shí)動態(tài)地選擇最合適的方法執(zhí)行。
  • 類與類之間的各種關(guān)系,也是面向?qū)ο蟮闹攸c(diǎn),合理地利用類與類之間的關(guān)系可以設(shè)計(jì)出很多很好的設(shè)計(jì)模式。

18、類與類之間的各種關(guān)系?

【繼承inheritance】A is-a B
類A繼承類B,UML類圖是實(shí)線加三角形由A指向B。A is-a B.
public繼承:父類中的成員訪問權(quán)限不變。
pretected繼承:父類的訪問權(quán)限中public權(quán)限降為protected,其他不變。
private繼承:父類中public和protected權(quán)限的成員全變?yōu)閜rivate,權(quán)限降級,即子類只有類成員變量和友元函數(shù)能夠訪問這些。
【依賴dependency】A use-a B.
類A依賴類B,UML類圖是虛線加箭頭由A指向B。A use-a B.
一般來說,依賴是指A的某些方法功能要用到B,常表現(xiàn)為B作為A的成員方法的形參或局部變量或返回值,即只和類成員方法有關(guān)。
【關(guān)聯(lián)association】A has-a B, B has-a A.
類A和類B雙向關(guān)聯(lián),UML類圖是A——B一根實(shí)線連接。A與B互相作為類的數(shù)據(jù)成員變量。
【組合composition】A has-a B 實(shí)例對象
類A組合了類B,UML類圖是A實(shí)心菱形再實(shí)線和箭頭指向B。類A中定義了類B作為數(shù)據(jù)成員,B在A中定義構(gòu)造。A和B的對象生命周期一樣。A擁有完整的B,強(qiáng)擁有關(guān)系。
【聚合aggregation】A has-a B 引用
類A聚合了類B,UML類圖是A空心菱形再實(shí)線和箭頭指向B。類A中定義了類B的指針作為數(shù)據(jù)成員,類B的實(shí)例化在其他地方。A和B的對象生命周期不一樣。A擁有不完整的B,弱擁有??梢哉J(rèn)為是 composition by reference == aggregation 或者也可以叫做委托delegation。

19、談?wù)勀懔私獾脑O(shè)計(jì)模式有哪些?

  • 設(shè)計(jì)模式本質(zhì)上是通過類與類之間的各種關(guān)系來實(shí)現(xiàn)的。
  • 【模板方法模式】技巧總結(jié):抽象父類+抽象方法+繼承+多態(tài)
  • 【單例模式】技巧總結(jié):聚合了一個(gè)自身的靜態(tài)對象指針+私有構(gòu)造+公有靜態(tài)getInstance()方法(懶漢模式)
  • 【簡單工廠模式】技巧總結(jié):繼承+依賴+多態(tài)+前后端分離
  • 【工廠方法模式】技巧總結(jié):繼承+依賴+多態(tài)+簡單工廠升級(增加具體的工廠子類)
  • 【策略模式】技巧總結(jié):繼承+依賴+多態(tài)+聚合(簡單工廠多一個(gè)聚合和多一個(gè)算法調(diào)用函數(shù))
  • 【觀察者模式】技巧總結(jié):繼承+聚合+依賴+多態(tài)

20、C++的拷貝構(gòu)造函數(shù)為什么要傳引用?

  • 如string類的拷貝構(gòu)造string( const string & str ){ ... }參數(shù)傳遞了引用,使用時(shí)string str2(str1);,內(nèi)部其實(shí)是先把實(shí)參通過引用傳遞給形參,const string & str=str1;沒有給str定義初始化,只是使用引用而已。
  • 如果進(jìn)行值傳遞string( const string str ){ ... },那么內(nèi)部其實(shí)是先有const string str=str1;等于是先給形參傳遞實(shí)參進(jìn)行定義構(gòu)造,就又得進(jìn)行一次拷貝構(gòu)造,同樣之后會再次進(jìn)行拷貝構(gòu)造,導(dǎo)致無限遞歸進(jìn)行拷貝構(gòu)造,直至爆棧程序結(jié)束。

21、如何防止一個(gè)類被拷貝?——拷貝分為拷貝構(gòu)造和拷貝賦值。

  • 1是將拷貝構(gòu)造函數(shù)和拷貝賦值函數(shù)設(shè)為private私有的,這樣的話在使用到拷貝構(gòu)造和拷貝賦值時(shí)編譯就會報(bào)錯(cuò)“設(shè)為私有的成員函數(shù)無法被對象訪問”。但是友元以及其他成員函數(shù)也還是可以使用(一般將成員函數(shù)中出現(xiàn)的本類對象默認(rèn)當(dāng)成是友元)。因此是非絕對的禁止拷貝。
  • 2是使用C++11的delete關(guān)鍵字,在拷貝構(gòu)造和拷貝賦值函數(shù)定義后面加上=delete;,如Test(const Test&) = delete;Test& operator=(const Test&) =delete;,那么就會將該類默認(rèn)提供的這兩個(gè)函數(shù)給delete刪除了,用的時(shí)候編譯會報(bào)錯(cuò)“無法引用已刪除的函數(shù)”。

22、帶指針成員變量的類編寫要注意什么?

  • 必須自定義拷貝構(gòu)造和拷貝賦值成員函數(shù),因?yàn)轭惸J(rèn)提供的這兩個(gè)函數(shù)只會完成對所有成員變量的數(shù)據(jù)淺拷貝,即指針也只會拷貝指針自身的值,而不會去深拷貝指針指向的內(nèi)容;因此在自定義的拷貝構(gòu)造和拷貝賦值成員函數(shù)中就要注意這個(gè)問題然后對指針指向的值就行深拷貝。還要注意,只要自定義了構(gòu)造、拷貝構(gòu)造、賦值構(gòu)造、析構(gòu)函數(shù),那么默認(rèn)的就自動失效,如果還想保留默認(rèn)的,需要=default。

23、什么是RAII 技術(shù)?

  • RAII(Resource Acquisition Is Initialization資源獲取時(shí)初始化)是一種利用對象生命周期來控制程序資源(如內(nèi)存、文件句柄、網(wǎng)絡(luò)連接、鎖互斥量、智能指針等等)的簡單技術(shù)。
  • RAII 的一般做法是這樣的:在對象構(gòu)造時(shí)獲取資源,接著控制對資源的訪問使之在對象的生命周期內(nèi)始終保持有效,最后在對象析構(gòu)的時(shí)候釋放資源。借此,我們實(shí)際上把管理一份資源的責(zé)任托管給了一個(gè)對象。這種做法有兩大好處:
    1、不需要顯式地釋放資源。
    2、采用這種方式,對象所需的資源在其生命期內(nèi)始終保持有效。
  • RAII在C++中的應(yīng)用非常廣泛,如C++標(biāo)準(zhǔn)庫中的lock_guard便是用RAII方式來控制互斥量,程序員可以非常方便地使用lock_guard,而不用擔(dān)心異常安全問題。
template <class Mutex> class lock_guard {
private:
   Mutex& mutex_;

public:
   lock_guard(Mutex& mutex) : mutex_(mutex) { mutex_.lock(); }
   ~lock_guard() { mutex_.unlock(); }

   lock_guard(lock_guard const&) = delete;
   lock_guard& operator=(lock_guard const&) = delete;
};

void write_to_file(const std::string & message)
{
   // 創(chuàng)建關(guān)于文件的互斥鎖
   static std::mutex mutex;

   // 在訪問文件前進(jìn)行加鎖,使用了局部對象lock
   std::lock_guard<std::mutex> lock(mutex);

   // 嘗試打開文件
   std::ofstream file("example.txt");
   if (!file.is_open())
       throw std::runtime_error("unable to open file");

   // 輸出文件內(nèi)容
   file << message << std::endl;

   // 當(dāng)離開作用域時(shí),文件句柄會被首先析構(gòu) (不管是否拋出了異常)
   // 互斥鎖lock對象也會被析構(gòu) (同樣地,不管是否拋出了異常)
}
  • 當(dāng)一個(gè)函數(shù)需要通過多個(gè)局部變量來管理資源時(shí),RAII就顯得非常好用。因?yàn)橹挥斜粯?gòu)造成功(構(gòu)造函數(shù)沒有拋出異常)的對象才會在返回時(shí)調(diào)用析構(gòu)函數(shù)[4],同時(shí)析構(gòu)函數(shù)的調(diào)用順序恰好是它們構(gòu)造順序的反序[5],這樣既可以保證多個(gè)資源(對象)的正確釋放,又能滿足多個(gè)資源之間的依賴關(guān)系。
    由于RAII可以極大地簡化資源管理,并有效地保證程序的正確和代碼的簡潔,所以通常會強(qiáng)烈建議在C++中使用它。

24.1、epoll如此高效的原因?

  • 這是由于我們在調(diào)用epoll_create時(shí),Linux內(nèi)核會創(chuàng)建一個(gè)eventpoll結(jié)構(gòu)體對象,內(nèi)核除了幫我們在epoll文件系統(tǒng)里建了個(gè)file結(jié)點(diǎn),在內(nèi)核cache里建了個(gè)【紅黑樹】用于存儲以后epoll_ctl傳來的要監(jiān)聽的socket事件外;還會再建立一個(gè)list鏈表,用于存儲準(zhǔn)備就緒的socket事件;當(dāng)epoll_wait調(diào)用時(shí),僅僅觀察這個(gè)list鏈表里有沒有數(shù)據(jù)(有事件發(fā)生的socket句柄)即可。有數(shù)據(jù)就返回,沒有數(shù)據(jù)就sleep,等到timeout時(shí)間到后即使鏈表沒數(shù)據(jù)也返回。所以,epoll_wait非常高效。

  • 這個(gè)準(zhǔn)備就緒list鏈表是怎么維護(hù)的呢?當(dāng)我們執(zhí)行epoll_ctl時(shí),除了把socket句柄放到epoll文件系統(tǒng)里file對象對應(yīng)的紅黑樹上之外,還會給內(nèi)核中斷處理程序注冊一個(gè)回調(diào)函數(shù),告訴內(nèi)核,如果這個(gè)句柄的中斷到了,就把它放到準(zhǔn)備就緒list鏈表里。所以,當(dāng)一個(gè)socket上有數(shù)據(jù)到了,內(nèi)核在把網(wǎng)卡上的數(shù)據(jù)copy到內(nèi)核中后就來把socket插入到準(zhǔn)備就緒鏈表里了。(注:好好理解這句話?。?/p>

  • 從上面這句可以看出,epoll的基礎(chǔ)就是回調(diào)呀!

  • 如此,一顆紅黑樹,一張準(zhǔn)備就緒句柄鏈表,少量的內(nèi)核cache,就幫我們解決了大并發(fā)下的socket處理問題。執(zhí)行epoll_create時(shí),創(chuàng)建了紅黑樹和就緒鏈表,執(zhí)行epoll_ctl時(shí),如果增加socket句柄,則檢查在紅黑樹中是否存在,存在立即返回,不存在則添加到樹干上,然后向內(nèi)核注冊回調(diào)函數(shù)。當(dāng)網(wǎng)卡監(jiān)聽到socket數(shù)據(jù)變化時(shí),就查找該紅黑樹是否有監(jiān)聽對應(yīng)的socket(紅黑樹查找速度很快),找到紅黑樹中有該socket時(shí)引發(fā)中斷事件,向準(zhǔn)備就緒鏈表中插入該socket句柄數(shù)據(jù)。執(zhí)行epoll_wait時(shí)立刻返回準(zhǔn)備就緒鏈表里的數(shù)據(jù)即可。
  • https://blog.csdn.net/tianjing0805/article/details/76021440

24.2、epoll的水平觸發(fā)和邊沿觸發(fā)?

  • 水平觸發(fā)(LT):當(dāng)epoll檢測到其上有事件發(fā)生并通知應(yīng)用程序時(shí),應(yīng)用程序可以不立即處理,這樣當(dāng)應(yīng)用程序再次調(diào)用epoll中調(diào)用函數(shù)時(shí),epoll會再次通知應(yīng)用程序此事件,直到被處理。
    邊沿觸發(fā)(ET):當(dāng)epoll檢測到其上有事件發(fā)生并通知應(yīng)用程序時(shí),應(yīng)用程序必須立即處理,并且下一次的epoll調(diào)用,不會再向應(yīng)用程序通知此事件。
    所以ET模式大大得降低了同一個(gè)epoll事件被重復(fù)觸發(fā)的次數(shù),因此ET模式工作效率比LT模式更高。
    select、poll、epoll的默認(rèn)工作模式都是水平觸發(fā)(LT)模式,但是epoll是支持邊沿觸發(fā)(ET)模式的。如果將ET模式代碼中的event.events = EPOLLIN | EPOLLET;改成event.events = EPOLLIN;,即使用LT模式??梢奅T,LT模式是針對具體的某個(gè)事件來設(shè)置的
    select/poll會因?yàn)楸O(jiān)聽fd的數(shù)量而導(dǎo)致效率低下,因?yàn)樗禽喸兯衒d,有數(shù)據(jù)就處理,沒數(shù)據(jù)就跳過,所以fd的數(shù)量會降低效率;而epoll只處理就緒的fd,它有一個(gè)就緒設(shè)備的鏈表,每次只輪詢該鏈表的數(shù)據(jù),然后進(jìn)行處理。
  • LT, ET這件事怎么做到的呢?當(dāng)一個(gè)socket句柄上有事件時(shí),內(nèi)核會把該句柄插入上面所說的準(zhǔn)備就緒list鏈表,這時(shí)我們調(diào)用epoll_wait,會把準(zhǔn)備就緒的socket拷貝到用戶態(tài)內(nèi)存,然后清空準(zhǔn)備就緒list鏈表,最后,epoll_wait干了件事,就是檢查這些socket,如果不是ET模式(就是LT模式的句柄了),并且這些socket上確實(shí)有未處理的事件時(shí),又把該句柄放回到剛剛清空的準(zhǔn)備就緒鏈表了。所以,非ET的句柄,只要它上面還有事件,epoll_wait每次都會返回這個(gè)句柄。(從上面這段,可以看出,LT還有個(gè)回放的過程,低效了)

25、Linux下的三個(gè)特殊進(jìn)程

  • Linux下有三個(gè)特殊的進(jìn)程idle進(jìn)程(PID=0),init進(jìn)程(PID=1),和kthreadd(PID=2)
  • idle(空閑)進(jìn)程由系統(tǒng)自動創(chuàng)建,運(yùn)行在內(nèi)核態(tài)
    idle進(jìn)程其pid=0,其前身是系統(tǒng)創(chuàng)建的第一個(gè)進(jìn)程,也是唯一一個(gè)沒有通過fork或者kernel_thread產(chǎn)生的進(jìn)程。完成加載系統(tǒng)后,演變?yōu)檫M(jìn)程調(diào)度、交換。
  • kthreadd進(jìn)程由idle通過kernel_thread創(chuàng)建,并始終運(yùn)行在內(nèi)核空間,負(fù)責(zé)所有內(nèi)核進(jìn)程的調(diào)度和管理。
    它的任務(wù)就是管理和調(diào)度其他內(nèi)核線程kernel_thread, 會循環(huán)執(zhí)行一個(gè)kthread的函數(shù),該函數(shù)的作用就是運(yùn)行kthread_create_list全局鏈表中維護(hù)的kthread, 當(dāng)我們調(diào)用kernel_thread創(chuàng)建的內(nèi)核線程會被加入到此鏈表中,因此所有的內(nèi)核線程都是直接或者間接的以kthreadd為父進(jìn)程 。
  • init進(jìn)程由idle通過kernel_thread創(chuàng)建,在內(nèi)核空間完成初始化后,加載init程序
    在這里我們就主要講解下init進(jìn)程,init進(jìn)程由0進(jìn)程創(chuàng)建,完成系統(tǒng)的初始化,是系統(tǒng)中所有其他用戶進(jìn)程的祖先進(jìn)程
    Linux中的所有進(jìn)程都是由init進(jìn)程創(chuàng)建并運(yùn)行的。首先Linux內(nèi)核啟動,然后在用戶空間中啟動init進(jìn)程,再啟動其他系統(tǒng)進(jìn)程。在系統(tǒng)啟動完成后,init將變成為守護(hù)進(jìn)程監(jiān)視系統(tǒng)其他進(jìn)程。
    所以說init進(jìn)程是Linux系統(tǒng)操作中不可缺少的程序之一,如果內(nèi)核找不到init進(jìn)程就會試著運(yùn)行/bin/sh,如果運(yùn)行失敗,系統(tǒng)的啟動也會失敗。

26、平衡二叉樹和紅黑樹的區(qū)別?

  • 平衡二叉樹AVL是指嚴(yán)格的平衡二叉樹——即每個(gè)樹結(jié)點(diǎn)的左右子樹的深度差值(平衡因子)不超過1。
  • 紅黑樹只是弱平衡二叉樹,不是完全遵守平衡因子不超過1的,也就是說可能會出現(xiàn)某結(jié)點(diǎn)左右子樹的深度差值大于1,因此紅黑樹是相對平衡的二叉樹。
  • 那為什么紅黑樹比AVL平衡二叉樹使用的地方更多呢?
  • 因?yàn)锳VL為了保持嚴(yán)格平衡,每次有數(shù)據(jù)插入或刪除后都需要做更多的檢查調(diào)整工作,效率較低。
    而紅黑樹不需要維持嚴(yán)格平衡,因此插入和刪除效率更高。
  • 就查找效率來說,AVL因?yàn)閲?yán)格平衡樹的深度大概率會比紅黑樹更小,因此查找效率會更高。
  • 總結(jié),AVL更適合于插入和刪除不頻繁而查找頻繁的地方;而紅黑樹兼顧了插入、刪除和查詢的性能,所以適用范圍更廣。

三、STL

1、STL的作用,為什么需要STL?(常用的是SGI版本的STL,被納入GNU C++標(biāo)準(zhǔn)庫)

  • 一是可復(fù)用性。STL標(biāo)準(zhǔn)模板庫,提供了一套常用的標(biāo)準(zhǔn)的數(shù)據(jù)結(jié)構(gòu)和算法程序以及和泛型模板編程結(jié)合,我們在寫程序時(shí),可以快速復(fù)用,而不需要重復(fù)的去造輪子,要站在巨人的肩膀上,節(jié)約時(shí)間。
  • 二是高效性。STL的代碼經(jīng)過多位C++大師的優(yōu)化,比絕大多數(shù)人自己再重寫相關(guān)的數(shù)據(jù)結(jié)構(gòu)和算法代碼效率更高,因此我們應(yīng)該盡量多用STL。
  • 研究STL源碼的意義在于,深入理解和學(xué)習(xí)優(yōu)秀的數(shù)據(jù)結(jié)構(gòu)和算法原理,然后擴(kuò)展輪子,或者是深入定制自己的輪子。
  • STL更注重的是模板泛型編程,而不是面向?qū)ο缶幊獭?/li>

2、STL六大組件之間的關(guān)系?

  • 六大組件:空間配置器(allocator)、容器(container)、迭代器(iterator)、算法(algorithm)、仿函數(shù)(functor)[函數(shù)對象]、適配器(adapter)[包裝器]。
  • 空間配置器為容器分配內(nèi)存,容器的模板參數(shù)中都有一個(gè)默認(rèn)的空間配置器class Alloc = alloc。最簡單的空間配置器實(shí)現(xiàn)就是類內(nèi)部用alllocate成員函數(shù)封裝::operator new(相當(dāng)于malloc)分配內(nèi)存,再用construct成員函數(shù)負(fù)責(zé)對象的構(gòu)造(實(shí)際上是借用定位new——placement new來給這塊內(nèi)存初始化的buffer = new(buffer) T(value);其中T(value)就用到了構(gòu)造函數(shù));用destroy成員函數(shù)完成析構(gòu),再用deallocate成員函數(shù)封裝::operator delete(相當(dāng)于free),來實(shí)現(xiàn)對象的內(nèi)存分配和構(gòu)造初始化以及用完后析構(gòu)和釋放內(nèi)存。而這些分步也是實(shí)現(xiàn)常用的new/delete表達(dá)式的步驟。STL提供的空間配置器分為第一級配置器和第二級配置器。
  • 容器分為序列式容器(vector/deque/list/forward_list)和關(guān)聯(lián)式容器(set/map/unordered_set/unordered_map)。
  • 迭代器作為容器和算法的粘合劑,算法函數(shù)的傳入?yún)?shù)一般都是容器的迭代器。指針也是一種特特殊的迭代器。迭代器有5中類型:
    1)input iterator:只讀入數(shù)據(jù)的迭代器,不能通過它修改指向的元素值
    2)output iterator:只寫入數(shù)據(jù)的迭代器
    3)forward iterator:包含上面的可讀可寫功能,然后只有operator++向前單步移動的功能。
    4)bidirectional iterator:包含上面的讀寫功能,可雙向單步移動,支持operator++和--。
    5)random access iterator:隨機(jī)存取迭代器,不僅可讀可寫,還支持順序存儲如用數(shù)組下標(biāo)的跳步移動訪問功能。
  • 算法
  • 仿函數(shù)又叫函數(shù)對象,常用類名加()代表一個(gè)臨時(shí)對象,可為算法動態(tài)地提供某種策略。類中一定重載了operator()。比如常用的將算法庫中的sort函數(shù)的比較策略用一個(gè)仿函數(shù)傳入,當(dāng)然也可傳入一個(gè)函數(shù)指針或是對類型重載<號。一般函數(shù)指針可以當(dāng)做仿函數(shù)對象用。
  • 適配器類的內(nèi)部封裝(組合)了一個(gè)其他類的對象。如容器適配器stack/queue默認(rèn)就是在內(nèi)部組合了一個(gè)deque<T> dq;,該deque對象的生命周期和stack/queue對象一樣,然后通過封裝dq對象的某些成員函數(shù)來實(shí)現(xiàn)自己的功能。priority_queue也是以vector為底層容器的完全二叉樹的容器適配器。前面說的三種容器適配器都不提供迭代器不能遍歷。還有仿函數(shù)適配器;迭代器適配器。

3、vector、map/multimap、unordered_map/unordered_multimap的底層數(shù)據(jù)結(jié)構(gòu),以及幾種map容器如何選擇?

  • 底層數(shù)據(jù)結(jié)構(gòu):vector基于數(shù)組,map/multimap基于紅黑樹,unordered_map/unordered_multimap基于哈希表。
    根據(jù)應(yīng)用場景進(jìn)行選擇:
    map/unordered_map 不允許重復(fù)元素
    multimap/unordered_multimap允許重復(fù)元素
    map/multimap底層基于紅黑樹,元素自動有序,且插入、刪除效率高
    unordered_map/unordered_multimap底層基于哈希表,故元素?zé)o序,查找效率高。

4、type_traits<T>是什么作用?

  • 這個(gè)的作用是對類型T進(jìn)行一些和類型相關(guān)的詢問,然后返回true或false的相關(guān)回答(即對類型T萃取出相關(guān)類型說明)。也有iterator_traits可以萃取出一個(gè)迭代器的相關(guān)類型。
    比如在空間配置器alloctor的實(shí)現(xiàn)中,有個(gè)destroy成員函數(shù)在對數(shù)組中的所有成員析構(gòu)時(shí),會先用type_traits<T>::has_trivial_destructor檢查成員類型的析構(gòu)函數(shù)是否是trivial(不重要的),若是返回__false_type則說明這個(gè)類型T的析構(gòu)函數(shù)是重要的,必須對數(shù)組中每個(gè)對象依次調(diào)用析構(gòu)函數(shù)。相反,若是返回__true_type說明確實(shí)是不重要的析構(gòu)函數(shù)(甚至是只有默認(rèn)隱藏的空析構(gòu)函數(shù)),則不需要去遍歷數(shù)組對象調(diào)用析構(gòu)函數(shù),可以大大提高效率。

5、STL的空間配置器實(shí)現(xiàn)原理?

  • STL空間配置器的作用是維護(hù)對象的內(nèi)存分配、釋放以及構(gòu)造、析構(gòu)。因此我們可以從這四個(gè)作用來看實(shí)現(xiàn)原理,空間配置器中一定實(shí)現(xiàn)了allocate/deallocate/contructor/destroy這四個(gè)成員函數(shù)。這幾個(gè)成員函數(shù)如何實(shí)現(xiàn)又要根據(jù)使用的空間配置器是第一級還是第二級。
  • STL的空間配置器分為第一級__malloc_alloc_template和第二級__default_alloc_template,第一級用于處理從堆中大塊內(nèi)存的申請(一次128字節(jié)以上算一大塊?),第二級用于處理從自定義的內(nèi)存池中取得小塊內(nèi)存的申請。默認(rèn)使用第二級,但是在第二級的allocate會判斷要申請的字節(jié)數(shù)n若是大于128則直接跳轉(zhuǎn)到第一級,否則再從第二級內(nèi)存池中對應(yīng)的鏈表中獲取合適的區(qū)塊。
  • 第一級配置器就是使用malloc/free/realloc這幾個(gè)C語言函數(shù)從堆中分配釋放內(nèi)存給用戶,并且自己寫了處理內(nèi)存不足的函數(shù)set_malloc_handle(),大致思想就是用一個(gè)無限循環(huán)來不斷嘗試分配內(nèi)存(可能程序在剛開始時(shí)就預(yù)先分配了大塊內(nèi)存?zhèn)溆?,這時(shí)候就可以釋放一部分出來解決內(nèi)存分配不足問題),直到分配成功。如果不寫這個(gè)處理函數(shù),那就只能返回內(nèi)存分配不足的異?;蛑苯油顺龀绦颉?/li>
  • 第二級配置器(又叫次層配置sub-allocation)是維護(hù)了一個(gè)內(nèi)存池,處理頻繁的小塊內(nèi)存的申請,相比第一級從堆中申請不僅速度更快還更有線程安全性,也能更好地處理內(nèi)存碎片的問題節(jié)約內(nèi)存。內(nèi)存池用一個(gè)數(shù)組free_list[16]維護(hù)了128/8=16個(gè)鏈表,分別存放區(qū)塊大小為{8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128}。首次從對應(yīng)鏈表中比如16申請區(qū)塊時(shí),會調(diào)用refill(n=16)函數(shù)先分配20個(gè)區(qū)塊給區(qū)塊大小n=16的鏈表備用。
  • void * allocate(size_t n)申請內(nèi)存過程:若是n>128那就轉(zhuǎn)到第一級配置器去調(diào)用malloc從堆中申請,否則默認(rèn)按照第二級配置器從內(nèi)存池申請。比如你要申請n=10的一塊內(nèi)存,就可以向上升級到8的倍數(shù)16那個(gè)free_list[16/8-1]的那個(gè)鏈表中獲取一個(gè)元素(區(qū)塊)。鏈表中的元素(區(qū)塊)obj也有意思,是一個(gè)union聯(lián)合體,在分配給用戶使用前將obj->free_list_link賦值給對應(yīng)鏈表的首元素my_free_list(由于該變量是obj * volatile * 類型,直接指向了實(shí)際內(nèi)存而不是存放在臨時(shí)寄存器中,因此后面給它重新復(fù)制會被立即修改,而不用擔(dān)心多線程問題),以便下次再從這個(gè)鏈表申請內(nèi)存時(shí)可以立即獲取一個(gè)空閑內(nèi)存塊。然后再分配給用戶使用,從第一個(gè)char字節(jié)開始的內(nèi)存。
    若是從鏈表中獲取不到內(nèi)存則要從內(nèi)存池中重新分配區(qū)塊到鏈表,若是內(nèi)存池中沒有了足夠的內(nèi)存那就要從堆中分配兩倍多的內(nèi)存到內(nèi)存池,若是堆中內(nèi)存不夠則可以拆分大區(qū)塊鏈表的內(nèi)存,若是都獲取不到內(nèi)存,那就轉(zhuǎn)到第一級配置的set_malloc_handle()去處理內(nèi)存分配失敗。
union obj{
    union obj * free_list_link;    //保存著下一個(gè)空閑區(qū)塊的地址
    char client_data;    //表示給用戶申請使用后的數(shù)據(jù)存放首字節(jié)地址
};
  • void deallocate(void *p, size_t n); 釋放內(nèi)存過程:若是n>128,則直接轉(zhuǎn)到第一級配置器去調(diào)用free釋放內(nèi)存到堆,否則默認(rèn)按照第二級配置器的流程歸還到內(nèi)存池中16個(gè)鏈表對應(yīng)的那個(gè)。
  • 內(nèi)存池設(shè)計(jì):兩根指針start_free和end_free分別代表池中自由的內(nèi)存的首字節(jié)和尾字節(jié)地址;將要申請的字節(jié)數(shù)擴(kuò)展到8的倍數(shù)的函數(shù);一個(gè)指針數(shù)組free_list維護(hù)16個(gè)鏈表的區(qū)塊的首地址;一個(gè)union obj表示一個(gè)區(qū)塊,內(nèi)含下一個(gè)區(qū)塊的指針和首地址;allocate分配內(nèi)存函數(shù)返回分配的區(qū)塊的首地址若是大于128則轉(zhuǎn)到第一級配置從malloc獲??;refill()函數(shù)重新填充該種區(qū)塊的鏈表,內(nèi)部調(diào)用chunk_alloc()函數(shù)從內(nèi)存池獲取一大塊內(nèi)存,再轉(zhuǎn)換成多個(gè)小區(qū)快(最多20塊)并循環(huán)鏈接起來;chunk_alloc()負(fù)責(zé)提供大塊的區(qū)塊,管理內(nèi)存池中剩余的區(qū)塊,不夠時(shí)從堆空間獲取兩倍多的需求填充內(nèi)存池;deallocate頭插法歸還內(nèi)存到對應(yīng)鏈表的首部。

6、STL中vector內(nèi)存動態(tài)擴(kuò)展的倍數(shù)是多少?設(shè)為多少比較合適?為什么

  • 不同的STL庫實(shí)現(xiàn),可能會設(shè)置為不同的倍數(shù),大都是設(shè)為2或者1.5倍。理論上來講設(shè)為1.5倍更好,更有利于后面擴(kuò)展內(nèi)存時(shí)復(fù)用前面釋放的內(nèi)存(重新從原開始釋放處向后取得一塊足夠大小的連續(xù)內(nèi)存)。

https://www.nowcoder.com/discuss/90907?type=2&order=0&pos=3&page=1
http://www.cnblogs.com/webary/p/4754522.html
https://blog.csdn.net/lisonglisonglisong/article/details/51327586

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容