一、C語言基礎(chǔ)
1橙垢、struct 的內(nèi)存對齊和填充問題
其實只要記住一個概念和三個原則就可以了:
- 一個概念:
自然對齊:如果一個變量的內(nèi)存起始地址正好位于它長度的整數(shù)倍,就被稱做自然對齊伦糯。
如果不自然對齊钢悲,會帶來CPU存取數(shù)據(jù)時的性能損失。(PS:具體應(yīng)該與CPU通過總線讀寫內(nèi)存數(shù)據(jù)的細(xì)節(jié)相關(guān)舔株,具體沒有細(xì)究) - 三個原則:
1)struct 的起始地址需要能夠被其成員中最寬的基本數(shù)據(jù)類型整除莺琳;
2)struct 的size 也必須能夠被其成員中最寬的基本數(shù)據(jù)類型整除;
3)struct 中每個成員地址相對于struct 的起始地址的offset载慈,必須是自然對齊的惭等。
填充的字節(jié)是隨意填充任意字符的,除非賦值前先對結(jié)構(gòu)體變量s進行memset(s, 0, sizeof(s));
進行整個內(nèi)存清零處理(全0填充办铡,也可以用其他字符全填充)辞做。
2、struct變量能進行比較嗎寡具?
- C語言不能直接用
==秤茅、>、>=童叠、<框喳、<=
這些進行比較课幕。但是C++若是對這些用于比較的關(guān)系運算符進行了重載(重載內(nèi)部可以調(diào)用memcmp函數(shù)),那就可以進行比較了五垮。 - 在對結(jié)構(gòu)體變量都用memset初始化的前提下乍惊,可以用memcmp函數(shù)進行一個一個字節(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一個一個字節(jié)復(fù)制過去的稼钩,而不是按照stu1.id=stu2.id;
這樣一個一個成員復(fù)制過去的。因此對于用=直接賦值的兩個變量达罗,再用memcmp比較坝撑,肯定是相等的。
3粮揉、運算符優(yōu)先級問題
- 優(yōu)先級排序:()
>
!非(其他單目的)>
算術(shù)運算符>
關(guān)系(> < == !=)運算符>
邏輯(& ^ | && ||)運算符>
賦值運算符>
逗號
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,否則報錯
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、手寫一個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--;
}
}
//從前往后正城虐拷貝賦值
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ù)寫法,在命令行運行可執(zhí)行文件時蕉扮,argc表示參數(shù)個數(shù)整胃,argv表示指針數(shù)組旧噪,數(shù)組里面每個元素都是一個字符串指針首地址淆九。 - 返回0表示程序正常退出,返回其他數(shù)字的含義由操作系統(tǒng)決定匆瓜。
- 在windows中再命令行執(zhí)行上面最簡單的代碼編譯承德可執(zhí)行文件
test.exe
后奔则,再執(zhí)行命令echo %ERRORLEVEL%
(windows命令行大小寫不敏感)可以輸出main函數(shù)的返回值:0蛮寂,修改為return -1;
實驗后也可以看到返回值為:-1。 - 在linux中可在執(zhí)行可執(zhí)行文件
./test
后易茬,再輸入一行命令echo $?
打印出main函數(shù)的返回值酬蹋。 - 在win的
test.exe && dir
或linux的./test && ls
,都可以根據(jù)可執(zhí)行文件的main返回值來判斷是否后面的系統(tǒng)命令是否繼續(xù)執(zhí)行抽莱,main返回值為0時才表示真范抓,則后面的列出當(dāng)前目錄命令都可以執(zhí)行。main返回其他值則后面的不繼續(xù)執(zhí)行食铐。
7匕垫、進程間通信的方式有哪些?
- 1虐呻、無名管道(父子進程兄弟進程使用
int pipe(int fd[2]);
fd[0]單收fd[1]單發(fā))象泵;
2、有名管道FIFO(任意兩進程間斟叼,是一種文件類型mkfifo("fifo1", 0666);
偶惠,文件標(biāo)識符fd = open("fifo1", O_WRONLY)
);管道的實質(zhì)是一個內(nèi)核緩沖區(qū)朗涩,管道一端的進程順序地將進程數(shù)據(jù)寫入緩沖區(qū)洲鸠,另一端的進程則順序地讀取數(shù)據(jù)。
3馋缅、共享內(nèi)存(key = ftok(".", 'z')
扒腕,創(chuàng)建并獲取共享內(nèi)存號shmid = shmget(key, 1024, IPC_CREAT|0666)
);采用共享內(nèi)存進行通信的一個主要好處是效率高萤悴,因為進程可以直接讀寫內(nèi)存瘾腰,而不需要任何數(shù)據(jù)的拷貝,對于像管道和消息隊列等通信方式覆履,則需要再內(nèi)核和用戶空間進行四次的數(shù)據(jù)拷貝蹋盆,而共享內(nèi)存則只拷貝兩次:一次從輸入進程到共享內(nèi)存區(qū)费薄,另一次從共享內(nèi)存到輸出進程。
4栖雾、消息隊列(需要內(nèi)核維護這個消息隊列key = ftok("/etc/passwd",'z')
楞抡,隊列號msqid = msgget(key, IPC_CREAT|0777)
);消息隊列克服了信號傳遞信息少析藕、管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺點召廷。消息隊列,就是一個消息的鏈表账胧,是一系列保存在內(nèi)核中消息的列表竞慢。用戶進程通過獲取消息隊列的唯一ID可以向消息隊列添加消息,也可以向消息隊列讀取消息治泥。
5筹煮、信號(如一些引發(fā)中斷信號kill);幾十種信號和以他們的名稱命名的常量居夹,通常程序中直接包含<signal.h>就好败潦。信號是在軟件層次上對中斷機制的一種模擬,是一種異步通信方式准脂,信號常用于用戶進程和內(nèi)核之間直接交互变屁。內(nèi)核也可以利用信號來通知用戶空間的進程來通知用戶空間發(fā)生了哪些系統(tǒng)事件。信號事件有兩個來源:1)硬件來源意狠,例如按下了cltr+C粟关,通常產(chǎn)生中斷信號sigint。2)軟件來源环戈,例如使用系統(tǒng)調(diào)用或者命令發(fā)出信號闷板。最常用的發(fā)送信號的系統(tǒng)函數(shù)是kill,raise函數(shù)。軟件來源還包括一些非法運算等操作院塞。
6遮晚、信號量(也是需要由內(nèi)核維護P獲取信號量操作減一,V釋放信號量操作加1拦止,只是同步計數(shù)器不傳數(shù)據(jù)key = ftok(".", 'z')
县遣,獲取信號量idsem_id = semget(key, 1, IPC_CREAT|0666)
);
7汹族、socket(可跨網(wǎng)絡(luò)主機進程通信)sys/socket.h 或 winsock2.h
http://www.reibang.com/p/21fba1542026 - socket網(wǎng)絡(luò)跨主機的進程間通信是以五元組【源IP萧求,源端口,目的IP顶瞒,目的端口夸政,TCP或UDP協(xié)議】,來找到不同的進程榴徐,并互發(fā)數(shù)據(jù)進行通信守问。
- 同一主機上的進程間通信匀归,大都是使用在本主機上唯一的key關(guān)鍵值來標(biāo)識一個可供不同進程訪問的資源,這個唯一的關(guān)鍵值又大都是與某一個實際存在的文件綁定耗帕,利用ftok()函數(shù)
key_t ftok( const char * fname, int id );
穆端,可根據(jù)fname即某文件名(一般是系統(tǒng)上一定存在的文件)找到它的索引節(jié)點號(唯一標(biāo)識一個文件的node_id)搭配提供的另一個參數(shù)id得到唯一的key值=(16進制的)id連接node_id
。因此不同的進程只要都遵守這個約定提供相同的參數(shù)給ftok()函數(shù)就都可以獲取到這個唯一的key_id仿便,然后根據(jù)key_id再獲取到對應(yīng)的進程間通信方式的id號体啰,然后對其進行讀寫數(shù)據(jù)操作。
類似的IPC有FIFO命名管道探越、消息隊列(前面兩個自帶進程同步效果)狡赐,信號量(只能控制進程同步窑业,要想互發(fā)數(shù)據(jù)需要結(jié)合共享內(nèi)存)钦幔、共享內(nèi)存 - 無名管道由于只在父子進程或兄弟進程間通信,可以在主進程中通過pipe(fd)隨機產(chǎn)生出管道的文件描述符常柄。不需要在主機上唯一鲤氢,因為只在這幾個相關(guān)的進程中私有。
8西潘、線程間的通信
- 共享的全局變量卷玉,然后利用全局的鎖變量或者信號量來實現(xiàn)同步互斥訪問共享的全局變量達(dá)到通信的目的。
- 消息傳遞(如窗口應(yīng)用的點擊按鈕事件傳遞)喷市、事件訂閱機制等
9相种、進程調(diào)度算法有哪些?Linux使用的什么進程調(diào)度方法品姓?
- 【解】進程調(diào)度算法有下面幾種:
先來先服務(wù)
短作業(yè)優(yōu)先
時間片輪轉(zhuǎn)
基于優(yōu)先級
Linux系統(tǒng)中寝并,進程分為實時和非實時兩種:
- 實時進程(相對于普通進程優(yōu)先級更高)
SCHED_FIFO —> 相同優(yōu)先級時,先來先服務(wù)腹备;不同優(yōu)先級衬潦,搶占式調(diào)度。進程一旦占用cpu則一直運行植酥,直到有更高優(yōu)先級任務(wù)到達(dá)或自己放棄镀岛。
SCHED_RR —> 時間片輪轉(zhuǎn),搶占式調(diào)度友驮。 - 普通進程
SCHED_OTHER —> priority(靜態(tài)優(yōu)先級)+counter(剩余時間片)之和作為動態(tài)優(yōu)先級漂羊,基于動態(tài)優(yōu)先級的時間片輪轉(zhuǎn)。
10卸留、虛擬內(nèi)存和物理內(nèi)存拨与?
- 先說說為什么會有虛擬內(nèi)存和物理內(nèi)存的
區(qū)別
。正在運行的一個進程艾猜,他所需的內(nèi)存是有可能大于內(nèi)存條容量之和的买喧,比如你的內(nèi)存條是256M捻悯,你的程序卻要創(chuàng)建一個2G的數(shù)據(jù)區(qū),那么不是所有數(shù)據(jù)都能一起加載到內(nèi)存(物理內(nèi)存)中淤毛,勢必有一部分?jǐn)?shù)據(jù)要放到其他介質(zhì)中(比如硬盤)今缚,待進程需要訪問那部分?jǐn)?shù)據(jù)時,再通過調(diào)度從磁盤進入物理內(nèi)存低淡。所以姓言,虛擬內(nèi)存是進程運行時所有內(nèi)存空間的總和,并且可能有一部分不在物理內(nèi)存中而在磁盤中蔗蹋,而物理內(nèi)存就是我們平時所了解的內(nèi)存條何荚。有的地方呢,也叫這個虛擬內(nèi)存為內(nèi)存交換區(qū)猪杭。
1)每個進程都有自己的虛擬內(nèi)存空間餐塘,所有進程共享物理內(nèi)存空間。
2)虛擬內(nèi)存大小和機器的位數(shù)有關(guān)皂吮,如32位的是4GB的地址總線(0~0xFFFFFFFF字節(jié))其中每個進程都有自己的0~0xBFFFFFFF的3GB虛擬內(nèi)存空間——用戶空間
戒傻,然后所有進程共用0xCFFFFFFF~0xFFFFFFFF這1GB的虛擬內(nèi)存空間——內(nèi)核空間
。64位的有2^64字節(jié)的地址總線蜂筹。
3)該圖顯示了兩個 64 位進程的虛擬地址空間:Notepad.exe 和 MyApp.exe需纳。每個進程都有其各自的虛擬地址空間,范圍從 0x000'0000000 至 0x7FF'FFFFFFFF(8TB用戶進程虛擬內(nèi)存空間)艺挪。每個陰影框都表示虛擬內(nèi)存或物理內(nèi)存的一個頁面(大小都為4KB)不翩。注意,Notepad 進程使用從 0x7F7'93950000 開始的虛擬地址的三個相鄰頁面麻裳。但虛擬地址的這三個相鄰頁面會映射到物理內(nèi)存中的非相鄰頁面
口蝠。而且還注意,兩個進程都使用從 0x7F7'93950000 開始的虛擬內(nèi)存頁面掂器,但這些虛擬頁面都映射到物理內(nèi)存的不同頁面亚皂,說明個進程間的虛擬內(nèi)存互不影響。
4)這種虛擬內(nèi)存一個頁page(4KB)和物理內(nèi)存一個頁幀(4KB)之間的映射關(guān)系国瓮,由操作系統(tǒng)的一個頁表來維護灭必,頁表中給各個頁都有編號(頁號和頁幀號對應(yīng)映射)。進程中虛擬內(nèi)存頁》頁表中的頁號—MMU內(nèi)存管理單元—頁表中的頁幀號《物理內(nèi)存中的頁幀
【由MMU的頁表來維護映射關(guān)系】
5)但是問題來了乃摹,虛擬內(nèi)存頁的個數(shù)=3GB/4KB > 物理內(nèi)存頁幀的個數(shù)=256MB/4KB禁漓,豈不是有些虛擬內(nèi)存頁的地址永遠(yuǎn)沒有對應(yīng)的物理內(nèi)存地址空間?不是的孵睬,操作系統(tǒng)是這樣處理的播歼。操作系統(tǒng)有個缺頁中斷(page fault)缺頁異常
功能。操作系統(tǒng)把暫時不訪問的物理內(nèi)存頁幀,讓他缺頁失效秘狞,并把它的原內(nèi)容寫入磁盤轉(zhuǎn)存起來——這個過程叫分頁-分頁是磁盤和內(nèi)存間傳輸數(shù)據(jù)塊的最小單位叭莫。
,隨后把當(dāng)前需要訪問的頁放到頁幀中烁试,并修改頁表中的映射雇初,這樣就保證所有的頁都有被調(diào)度的可能了。當(dāng)進程又要訪問原轉(zhuǎn)存的內(nèi)容减响,會先訪問原物理內(nèi)存頁但是引發(fā)缺頁中斷然后從磁盤中取出頁內(nèi)容到物理內(nèi)存頁靖诗,然后進程訪問到數(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)限來保護實際對應(yīng)的物理地址的數(shù)據(jù)促绵。
- 程序可以使用一系列相鄰的虛擬地址來訪問物理內(nèi)存中不相鄰的大內(nèi)存緩沖區(qū)。
- 不同進程使用的虛擬地址彼此隔離据途。一個進程中的代碼無法更改正在由另一進程或操作系統(tǒng)使用的物理內(nèi)存绞愚。
- 程序可以使用一系列虛擬地址來訪問大于可用物理內(nèi)存的內(nèi)存緩沖區(qū)叙甸。當(dāng)物理內(nèi)存的供應(yīng)量變小時颖医,內(nèi)存管理器會將(暫時不用的)物理內(nèi)存頁(通常大小為 4 KB)保存到磁盤文件,從而空出可用的物理內(nèi)存裆蒸。數(shù)據(jù)或代碼頁會根據(jù)需要在物理內(nèi)存與磁盤之間移動熔萧。
12、死鎖產(chǎn)生的四個條件僚祷,死鎖發(fā)生后怎么檢測和恢復(fù)佛致?
- 死鎖的四個必要條件:
互斥條件
:一個資源每次只能被一個進程使用。
請求與保持條件:
一個進程在申請新的資源的同時保持對原有資源的占有辙谜。
不剝奪條件:
進程已獲得的資源俺榆,在未使用完之前,不能強行剝奪装哆。
循環(huán)等待條件:
若干進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系罐脊。 - 死鎖發(fā)生后:
檢測死鎖:首先為每個進程和每個資源指定一個唯一的號碼,然后建立資源分配表和進程等待表蜕琴。
解除死鎖:當(dāng)發(fā)現(xiàn)有進程死鎖后萍桌,可以直接撤消死鎖的進程或撤消代價最小的進程,直至有足夠的資源可用凌简,死鎖狀態(tài)消除為止上炎。
13、不用中間變量實現(xiàn)兩個元素交換值雏搂。
void swap1(int &a, int &b)
{ //異或操作只適用于整型數(shù)藕施,不能用于小數(shù)
if (a != b) //如果兩個數(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ō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ù)
}
- 給出一個16位的無符號整數(shù)享扔。稱這個二進制數(shù)的前8位為“高位”底桂,后8位為“低位”。現(xiàn)在寫一程序?qū)⑺母叩臀唤粨Q惧眠。
a = (a >> 8) | (a << 8);
15籽懦、守護、僵尸氛魁、孤兒進程的概念暮顺?
-
守護進程:
運行在后臺的一種特殊進程,獨立于控制終端并周期性地執(zhí)行某些任務(wù)秀存。如何實現(xiàn)守護進程捶码?(孤兒進程)
僵尸進程:
一個進程 fork 子進程,子進程退出或链,而父進程沒有 wait/waitpid子進程惫恼,那么子進程的進程描述符仍保存在系統(tǒng)中,這樣的進程稱為僵尸進程澳盐。
孤兒進程:
一個父進程退出祈纯,而它的一個或多個子進程還在運行,這些子進程稱為孤兒進程洞就。(孤兒進程將由 init 進程(進程號pid為1)收養(yǎng)并對它們完成狀態(tài)收集工作)
16盆繁、一個進程的主線程結(jié)束后,其他線程怎么辦旬蟋?
- 通常一個進程的主線程是指的main()函數(shù)啟動后的那個線程油昂。實際上一個進程的所有線程是平等的,沒有主線程子線程之分。
- 如果讓main()函數(shù)執(zhí)行到最后一行(默認(rèn)return 0;)或return其他值來結(jié)束主線程冕碟,然后調(diào)用glibc庫函數(shù)exit拦惋,exit進行相關(guān)清理工作后調(diào)用_exit系統(tǒng)調(diào)用退出該進程。因為進程結(jié)束了安寺,所以該進程所有的線程也就結(jié)束了厕妖。
- 如果main()主線程在沒有運行到return退出前就由
pthread_cancel(tid);
終止了,那么就不會引發(fā)進程結(jié)束挑庶,其他的線程可以繼續(xù)正常運行言秸。而所謂的線程之間共享的全局變量是在main()函數(shù)之外也就是主線程之外的,因此主線程main函數(shù)內(nèi)的局部變量銷毀對其他線程也無影響迎捺。 - 如果系統(tǒng)調(diào)用#kill 某線程號 举畸,也是可以結(jié)束整個進程的。
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)程序長時間運行時率挣,其破壞力是驚人的 - 從性能下降到內(nèi)存耗盡,甚至?xí)绊懫渌绦虻恼_\行辅辩。
- 內(nèi)存泄漏的避免:
一是
從編程習(xí)慣來談要求我們記得在同一個代碼塊成對使用malloc/free或new/delete或new[]數(shù)組/delete[]數(shù)組难礼,最好不要在函數(shù)內(nèi)申請內(nèi)存然后返回指針娃圆,這樣在調(diào)用函數(shù)后很容易忘記這個指針指向的內(nèi)存是堆內(nèi)存而忘記手動釋放玫锋;而是應(yīng)該在函數(shù)外申請內(nèi)存,然后將指針作為參數(shù)傳遞給函數(shù)使用讼呢,這樣在函數(shù)使用完后我們還可以從上面代碼看到這個指針是指向堆內(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)使用父類指針指向子類對象時,析構(gòu)的時候會無法調(diào)用子類的析構(gòu)函數(shù)釋放子類的資源看蚜,從而造成泄漏叫搁。 - 內(nèi)存泄漏發(fā)生后的定位:
一是
先找到發(fā)生內(nèi)存泄漏的程序在linux下可以用top命令列出正在運行的程序占用的內(nèi)存,windows下可以查看任務(wù)管理器,從中分析出異常的程序渴逻,然后通過#ps aux| grep 程序名》得到進程號pid》然后#kill pid結(jié)束這個進程疾党;二是
尋找程序中引發(fā)內(nèi)存泄漏的代碼,若是代碼量不大惨奕,可以通過人工分析是否malloc/new對應(yīng)的指針都得到了正確的釋放雪位,然后重點檢查程序會循環(huán)執(zhí)行的那些代碼就是引起內(nèi)存泄漏一直增長的地方。如果還是找不到梨撞,那就需要借助一些工具來定位雹洗,比如使用vs編程可以安裝一個VLD(Visual Leak Detector)插件,使用的時候#include <vld.h>
就可以了調(diào)試程序看到代碼檢查的報錯結(jié)果卧波。在linux下可以使用 Valgrind Memcheck工具的使用方式如下:valgrind --tool=memcheck ./a.out
其中a.out為要檢查的程序編譯生成的可執(zhí)行文件(為了使valgrind發(fā)現(xiàn)的錯誤更精確队伟,能夠定位到源代碼行,建議在編譯時加上-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)其實可以分解為兩部击吱,即先調(diào)用new操作符(operator new)申請內(nèi)存,再調(diào)用placement new來初始化對象遥昧。即上面對new表達(dá)式的使用
string * str = new string("hello");
等價于下面兩句覆醇。
void *buffer = ::operator new(sizeof(string));
buffer = new(buffer) string(“hello”);
- 而operator new函數(shù)相當(dāng)于是malloc,其實它的內(nèi)部實現(xiàn)也就是調(diào)用了
void * malloc(size_t size);
函數(shù)炭臭。
void* operator new(size_t sizt)
{
return malloc(size);
}
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ù)處理時被重復(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ù)
int getReal() const { return real; }
- 作用是表示此類成員函數(shù)不能改變所有類成員變量的值竖独。
- 當(dāng)定義一個const的類對象時
const complex con1;
奇徒,此對象con1就只能調(diào)用這些const成員函數(shù)干像,調(diào)用非const成員函數(shù)就會報錯赖歌。
5构资、C++內(nèi)存分區(qū)割坠?進程虛擬內(nèi)存分布齐帚?共有5個分區(qū)
6、一個進程可用的內(nèi)存大小彼哼、線程可用內(nèi)存大卸酝?堆內(nèi)存多大敢朱?棧內(nèi)存多大剪菱?
- 根據(jù)上面這張圖可以分析出,一個用戶進程最多可用3GB內(nèi)存拴签,進程里既有堆又有棧孝常。而一個線程只有自己的棧空間蚓哩,所以可用的最大棧內(nèi)存也就是上圖說的8MB或10MB(可手動修改)构灸。因此一個最多可開辟3GB/10MB=300個線程。
- 對于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編程時debug和release版本有啥區(qū)別?
- Debug通常稱為調(diào)試版本次兆,它包含很多調(diào)試信息稿茉,并且不作任何優(yōu)化,便于程序員調(diào)試程序(加斷點實時查看變量值)芥炭。會檢查assert斷言語句。也可關(guān)閉assert斷言——先
#define NDEBUG
后#include<assert.h>
恃慧,就可以將后面寫的那些assert斷言語句關(guān)閉檢查园蝠。 - Release稱為發(fā)布版本,它往往是進行了各種優(yōu)化(比如調(diào)整某些變量在棧內(nèi)存的地址順序)痢士,使得程序在代碼大小和運行速度上都是最優(yōu)的彪薛,以便用戶很好的使用茂装。會忽略assert斷言語句。
8善延、面向?qū)ο蟮乃拇筇匦灾v一下少态?
- 抽象:將多種事物抽象出共同特征形成一個抽象父類。
- 封裝:將變量和對變量的操作組合在一起形成一個類或者一個模塊易遣,就是封裝彼妻。一處封裝,多處調(diào)用豆茫。目的是代碼復(fù)用侨歉。
- 繼承:子類在原有父類代碼的基礎(chǔ)上進行擴展。目的是代碼復(fù)用揩魂。
- 多態(tài):調(diào)用同名函數(shù)幽邓,卻因為上下文環(huán)境不同,而會有不同的實現(xiàn)火脉。一個函數(shù)接口多種實現(xiàn)牵舵。目的是接口復(fù)用。
9倦挂、C++多態(tài)分幾類棋枕?多態(tài)有幾種實現(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++代碼在編譯是就會將這個add函數(shù)原型用C的形式編譯成_add
。然后就和原C的動態(tài)庫函數(shù)名對上了哥桥。否則C++代碼中編譯成_add_int_int
在調(diào)用C動態(tài)庫時會報錯找不到該函數(shù)辙浑。 - C要調(diào)用C++的動態(tài)庫時,必須修改原C++代碼拟糕,將要被C調(diào)用的函數(shù)原型修改為
extern "C" int add(int a, int b);
判呕,就可以被編譯成C形式了倦踢,然后重新編譯就可供C調(diào)用。編譯完成后侠草,提供給c使用的頭文件里面不能包含extern “C”辱挥,可以使用宏開關(guān)解決,也可以重新寫個頭文件边涕。當(dāng)然還有一點就是該函數(shù)內(nèi)部不能包含用C++特有的東西晤碘。 - 總結(jié),要想互相能調(diào)用奥吩,都只需要對C++代碼進行修改哼蛆,函數(shù)原型前加
extern "C"
,而C代碼不需要修改霞赫。
11腮介、從源代碼到可執(zhí)行文件經(jīng)歷了那幾步?
- 文本源代碼》預(yù)處理(得去注釋端衰、宏替換叠洗、展開頭文件等的文本源碼》編譯(得文本匯編代碼.S)》匯編(得二進制的.o目標(biāo)文件)》鏈接多個.o(得到二進制可執(zhí)行文件)
12、C和C++的區(qū)別
- C語言更加注重的是過程旅东,C++除了保留C語言的優(yōu)點灭抑,還增加了面向?qū)ο蟮臋C制。
- 應(yīng)用場景來看抵代,由于C沒有面向?qū)ο筇诮冢栽诔绦蛞?guī)模太大的時候?qū)懫饋砗軓?fù)雜,面向?qū)ο蟮腃++更適合大規(guī)模的程序荤牍。
- C沒類class面向?qū)ο蟾嗟氖敲嫦蜻^程編程案腺,struct的用法也有差異,比如定義struct變量的時候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就是用來進行內(nèi)存管理的颅眶,不需要程序員自己手動開關(guān)。
- Java呢田弥,摒棄了C++很多復(fù)雜的特性涛酗,比如指針、多繼承偷厦、操作符重載等等商叹,相對來說Java的編程學(xué)習(xí)入門比較容易。
14只泼、static_cast 和 dynamic_cast 的區(qū)別剖笙?兩個操作符
- cast轉(zhuǎn)換發(fā)生的時間不同,一個是static靜態(tài)編譯時请唱,一個是動態(tài)運行時弥咪;
- static_cast是相當(dāng)于C的強制類型轉(zhuǎn)換,用起來可能有一點危險十绑,不提供運行時的檢查來確保轉(zhuǎn)換的安全性聚至。
- dynamic_cast用于轉(zhuǎn)換指針和和引用,不能用來轉(zhuǎn)換對象 —— 主要用于類層次間的上行轉(zhuǎn)換和下行轉(zhuǎn)換本橙,還可以用于類之間的交叉轉(zhuǎn)換扳躬。在類層次間進行上行轉(zhuǎn)換時,dynamic_cast和static_cast的效果是一樣的甚亭;在進行下行轉(zhuǎn)換時贷币,dynamic_cast具有類型檢查的功能,比static_cast更安全狂鞋。在多態(tài)類型之間的轉(zhuǎn)換主要使用dynamic_cast片择,因為類型提供了運行時信息。
15骚揍、說幾個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á)式:是一個匿名仿函數(shù)(函數(shù)對象)嘲叔,一定是[]中括號開頭。[可選的捕捉到的外部變量] (可選的形參傳遞)mutable可選 throwSpec可選 -> retType可選 { ... }
抽活,當(dāng)中括號和大括號之間有東西時硫戈,也一定要有小括號。
4)override 和 final 關(guān)鍵字:兩個繼承控制關(guān)鍵字下硕,override明確地表示一個函數(shù)是對基類中一個虛函數(shù)的重寫丁逝,virtual void func(int) override;
確保在派生類中聲明的重載函數(shù)跟基類的虛函數(shù)有相同的簽名汁胆。final阻止類的進一步派生class TaskManager {/*..*/} final;
和虛函數(shù)的進一步重載virtual void func(int) override final;
。
5)空指針常量nullptr霜幼。C語言中NULL其實是define成0嫩码,所以兩者等價,但是因為C++支持函數(shù)重載罪既,那么在遇到函數(shù)f(int)
和f(void*)
時铸题,f(0)和f(NULL)就會產(chǎn)生歧義不明確指向的是哪個,而有了f(nullptr)后就明確了是指向f(void*)琢感。
6)智能指針(模板類)shared_ptr(附帶weak_ptr)丢间、unique_ptr。shared_ptr采用引用計數(shù)的方式管理所指向的對象驹针。當(dāng)有一個新的shared_ptr指向同一個對象時(復(fù)制shared_ptr等)烘挫,引用計數(shù)加1。當(dāng)shared_ptr離開作用域時牌捷,引用計數(shù)減1墙牌。當(dāng)引用計數(shù)為0時,釋放所管理的內(nèi)存暗甥。weak_ptr一般和shared_ptr配合使用喜滨。它可以指向shared_ptr所指向的對象,但是卻不增加對象的引用計數(shù)撤防。這樣就有可能出現(xiàn)weak_ptr所指向的對象實際上已經(jīng)被釋放了的情況虽风。因此,weak_ptr有一個lock函數(shù)寄月,嘗試取回一個指向?qū)ο蟮膕hared_ptr辜膝。unique_ptr對于所指向的對象,正如其名字所示漾肮,是獨占的厂抖。所以,不可以對unique_ptr進行拷貝克懊、賦值等操作忱辅,但是可以通過release函數(shù)在unique_ptr之間轉(zhuǎn)移控制權(quán)。上述對于拷貝的限制谭溉,有兩個特殊情況墙懂,即unique_ptr可以作為函數(shù)的返回值和參數(shù)使用,這時雖然也有隱含的拷貝存在扮念,但是并非不可行的损搬。
1、所有的智能指針類都有一個explicit構(gòu)造函數(shù),以普通指針作為參數(shù)巧勤。因此不能自動將普通指針轉(zhuǎn)換為智能指針對象嵌灰,必須顯式調(diào)用構(gòu)造函數(shù)。 2踢关、所有智能指針都是對原始指針執(zhí)行淺拷貝伞鲫,即所有指針指向同一塊內(nèi)存粘茄,而不是深拷貝創(chuàng)建多個備份签舞。 3、當(dāng)一個shared_ptr對象sp2是由sp1拷貝構(gòu)造或者賦值構(gòu)造得來的時候柒瓣,實際上構(gòu)造完成后sp1內(nèi)部的__shared_count對象包含的指向管理對象的指針與sp2內(nèi)部的__shared_count對象包含的指向管理對象的指針是相等的儒搭,也就是說當(dāng)多個shared_ptr對象來管理同一個對象時,它們共同使用同一個動態(tài)分配的管理對象指針芙贫。 4搂鲫、都是運用了RAII技術(shù)來實現(xiàn)的。原生指針被轉(zhuǎn)為智能指針類使用之后磺平,就不要再使用原生指針魂仍,否則容易出錯。即不要把一個原生指針給多個shared_ptr管理拣挪。 5擦酌、引用頭文件#include<memory>。智能指針是模板類而不是指針菠劝。 6赊舶、智能指針實質(zhì)就是重載了->和*操作符的類,由類來實現(xiàn)對內(nèi)存的管理赶诊,確保即使有異常產(chǎn)生笼平,也可以通過智能指針類的析構(gòu)函數(shù)完成內(nèi)存的釋放。
7)initializer_list<>初始化列表舔痪,用于給變量定義時用{}賦初值寓调,如vector<int> v1{2,3,5};或者是直接用于函數(shù)的參數(shù)列表如max({5,2,8,1})得到8。這種對應(yīng)于{}大括號的初始化列表锄码,底層是一個array容器夺英,利用這個array先把初值開辟內(nèi)存,然后再調(diào)用對應(yīng)的接受initializer_list<>形參的構(gòu)造函數(shù)巍耗,然后進行數(shù)據(jù)的淺拷貝秋麸。
8)explicit關(guān)鍵字,多用于構(gòu)造函數(shù)前炬太,明確表示該構(gòu)造函數(shù)必須被顯示的調(diào)用灸蟆,而不能被用于隱式轉(zhuǎn)換調(diào)用(如多個參數(shù)中后面的參數(shù)有默認(rèn)值時,只給出前面參數(shù)一般也會觸發(fā)隱式轉(zhuǎn)換構(gòu)造),現(xiàn)在加了explicit關(guān)鍵字就是為了禁止這種隱式的轉(zhuǎn)換炒考。
9)decltype關(guān)鍵字可缚,用于從操作的對象或表達(dá)式中得到類型,如decltype(lambda)
表示得到一個lambda對象的類型斋枢。
用途一:用于指明模板函數(shù)的返回類型帘靡。
用途二:用于傳遞lambda表達(dá)式對象的類型。
10)move移動語義瓤帚,對應(yīng)于右值引用描姚,可以將右值(只能放在賦值等號=右邊的變量,std::move(非右值)可以強制轉(zhuǎn)變?yōu)橛抑担┑馁Y源(內(nèi)存空間和值)竊取再直接賦給左值戈次,使得左值不用再定義和開辟額外的內(nèi)存轩勘,加快move拷貝的速度。常用于在構(gòu)造函數(shù)的時候新增一個類似于拷貝構(gòu)造(
string(const string &str){ ... }
)的move構(gòu)造(string(string &&str){ ... }
)怯邪。本質(zhì)上是淺拷貝的行為绊寻,即拷貝的時候只是將原指針的值(變量內(nèi)存地址)賦給新的指針,然后原指針賦NULL不再使用悬秉,實際的那塊內(nèi)存沒有動澄步。相對的深拷貝就是對內(nèi)存中每一個數(shù)據(jù)都重新拷貝到了一塊新的內(nèi)存。https://blog.csdn.net/FX677588/article/details/70157088
https://www.cnblogs.com/guxuanqing/p/6707824.html
16和泌、如何在一個不安全的環(huán)境中實現(xiàn)安全的數(shù)據(jù)通信村缸?
- 要實現(xiàn)數(shù)據(jù)的安全傳輸,當(dāng)然就要對數(shù)據(jù)進行加密了允跑。
- 如果使用
對稱加密算法
王凑,加解密使用同一個密鑰,除了自己保存外聋丝,對方也要知道這個密鑰索烹,才能對數(shù)據(jù)進行解密。如果你把密鑰也一起傳過去弱睦,就存在密碼泄漏的可能百姓。所以我們使用非對稱加密算法
,過程如下:
首先 數(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ù)據(jù)成員和方法成員進行封裝量蕊,對抽象基類的繼承可以很好的復(fù)用基類封裝的數(shù)據(jù)和方法成員,多態(tài)的應(yīng)用可以使父類指針指向子類的實例對象從而在程序運行時動態(tài)地選擇最合適的方法執(zhí)行摩疑。
- 類與類之間的各種關(guān)系危融,也是面向?qū)ο蟮闹攸c,合理地利用類與類之間的關(guān)系可以設(shè)計出很多很好的設(shè)計模式雷袋。
18、類與類之間的各種關(guān)系辞居?
【繼承inheritance】A is-a B
類A繼承類B楷怒,UML類圖是實線加三角形由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一根實線連接锻离。A與B互相作為類的數(shù)據(jù)成員變量。
【組合composition】A has-a B 實例對象
類A組合了類B墓怀,UML類圖是A實心菱形再實線和箭頭指向B汽纠。類A中定義了類B作為數(shù)據(jù)成員,B在A中定義構(gòu)造傀履。A和B的對象生命周期一樣虱朵。A擁有完整的B,強擁有關(guān)系。
【聚合aggregation】A has-a B 引用
類A聚合了類B卧秘,UML類圖是A空心菱形再實線和箭頭指向B呢袱。類A中定義了類B的指針作為數(shù)據(jù)成員,類B的實例化在其他地方翅敌。A和B的對象生命周期不一樣羞福。A擁有不完整的B,弱擁有蚯涮≈巫ǎ可以認(rèn)為是 composition by reference == aggregation 或者也可以叫做委托delegation。
19遭顶、談?wù)勀懔私獾脑O(shè)計模式有哪些张峰?
- 設(shè)計模式本質(zhì)上是通過類與類之間的各種關(guān)系來實現(xiàn)的。
- 【模板方法模式】技巧總結(jié):抽象父類+抽象方法+繼承+多態(tài)
- 【單例模式】技巧總結(jié):聚合了一個自身的靜態(tài)對象指針+私有構(gòu)造+公有靜態(tài)getInstance()方法(懶漢模式)
- 【簡單工廠模式】技巧總結(jié):繼承+依賴+多態(tài)+前后端分離
- 【工廠方法模式】技巧總結(jié):繼承+依賴+多態(tài)+簡單工廠升級(增加具體的工廠子類)
- 【策略模式】技巧總結(jié):繼承+依賴+多態(tài)+聚合(簡單工廠多一個聚合和多一個算法調(diào)用函數(shù))
- 【觀察者模式】技巧總結(jié):繼承+聚合+依賴+多態(tài)
20棒旗、C++的拷貝構(gòu)造函數(shù)為什么要傳引用喘批?
- 如string類的拷貝構(gòu)造
string( const string & str ){ ... }
參數(shù)傳遞了引用,使用時string str2(str1);
铣揉,內(nèi)部其實是先把實參通過引用傳遞給形參饶深,const string & str=str1;
沒有給str定義初始化,只是使用引用而已逛拱。 - 如果進行值傳遞
string( const string str ){ ... }
敌厘,那么內(nèi)部其實是先有const string str=str1;
等于是先給形參傳遞實參進行定義構(gòu)造,就又得進行一次拷貝構(gòu)造朽合,同樣之后會再次進行拷貝構(gòu)造俱两,導(dǎo)致無限遞歸進行拷貝構(gòu)造,直至爆棧程序結(jié)束曹步。
21宪彩、如何防止一個類被拷貝?——拷貝分為拷貝構(gòu)造和拷貝賦值箭窜。
- 1是將拷貝構(gòu)造函數(shù)和拷貝賦值函數(shù)設(shè)為private私有的毯焕,這樣的話在使用到拷貝構(gòu)造和拷貝賦值時編譯就會報錯“設(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)提供的這兩個函數(shù)給delete刪除了簸喂,用的時候編譯會報錯“無法引用已刪除的函數(shù)”块差。
22侵续、帶指針成員變量的類編寫要注意什么倔丈?
- 必須自定義拷貝構(gòu)造和拷貝賦值成員函數(shù),因為類默認(rèn)提供的這兩個函數(shù)只會完成對所有成員變量的數(shù)據(jù)淺拷貝状蜗,即指針也只會拷貝指針自身的值需五,而不會去深拷貝指針指向的內(nèi)容;因此在自定義的拷貝構(gòu)造和拷貝賦值成員函數(shù)中就要注意這個問題然后對指針指向的值就行深拷貝轧坎。還要注意宏邮,只要自定義了構(gòu)造、拷貝構(gòu)造缸血、賦值構(gòu)造蜜氨、析構(gòu)函數(shù),那么默認(rèn)的就自動失效捎泻,如果還想保留默認(rèn)的飒炎,需要=default。
23笆豁、什么是RAII 技術(shù)郎汪?
- RAII(Resource Acquisition Is Initialization資源獲取時初始化)是一種利用對象生命周期來控制程序資源(如內(nèi)存、文件句柄渔呵、網(wǎng)絡(luò)連接怒竿、鎖互斥量、智能指針等等)的簡單技術(shù)扩氢。
- RAII 的一般做法是這樣的:在對象構(gòu)造時獲取資源,接著控制對資源的訪問使之在對象的生命周期內(nèi)始終保持有效爷辱,最后在對象析構(gòu)的時候釋放資源录豺。借此,我們實際上把管理一份資源的責(zé)任托管給了一個對象饭弓。這種做法有
兩大好處:
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;
// 在訪問文件前進行加鎖棚菊,使用了局部對象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)離開作用域時,文件句柄會被首先析構(gòu) (不管是否拋出了異常)
// 互斥鎖lock對象也會被析構(gòu) (同樣地叔汁,不管是否拋出了異常)
}
- 當(dāng)一個函數(shù)需要通過多個局部變量來管理資源時统求,RAII就顯得非常好用检碗。因為只有被構(gòu)造成功(構(gòu)造函數(shù)沒有拋出異常)的對象才會在返回時調(diào)用析構(gòu)函數(shù)[4],同時析構(gòu)函數(shù)的調(diào)用順序恰好是它們構(gòu)造順序的反序[5]码邻,這樣既可以保證多個資源(對象)的正確釋放折剃,又能滿足多個資源之間的依賴關(guān)系。
由于RAII可以極大地簡化資源管理像屋,并有效地保證程序的正確和代碼的簡潔怕犁,所以通常會強烈建議在C++中使用它。
24.1开睡、epoll如此高效的原因因苹?
這是由于我們在調(diào)用epoll_create時,Linux內(nèi)核會創(chuàng)建一個eventpoll結(jié)構(gòu)體對象篇恒,內(nèi)核除了幫我們在epoll文件系統(tǒng)里建了個file結(jié)點扶檐,在內(nèi)核cache里建了個【紅黑樹】用于存儲以后epoll_ctl傳來的要監(jiān)聽的socket事件外;還會再建立一個list鏈表胁艰,用于存儲準(zhǔn)備就緒的socket事件款筑;當(dāng)epoll_wait調(diào)用時,僅僅觀察這個list鏈表里有沒有數(shù)據(jù)(有事件發(fā)生的socket句柄)即可腾么。有數(shù)據(jù)就返回奈梳,沒有數(shù)據(jù)就sleep,等到timeout時間到后即使鏈表沒數(shù)據(jù)也返回解虱。所以攘须,epoll_wait非常高效。
這個準(zhǔn)備就緒list鏈表是怎么維護的呢殴泰?當(dāng)我們執(zhí)行epoll_ctl時于宙,除了把socket句柄放到epoll文件系統(tǒng)里file對象對應(yīng)的紅黑樹上之外,還會給內(nèi)核中斷處理程序注冊一個回調(diào)函數(shù)悍汛,告訴內(nèi)核捞魁,如果這個句柄的中斷到了,就把它放到準(zhǔn)備就緒list鏈表里离咐。所以谱俭,當(dāng)一個socket上有數(shù)據(jù)到了,內(nèi)核在把網(wǎng)卡上的數(shù)據(jù)copy到內(nèi)核中后就來把socket插入到準(zhǔn)備就緒鏈表里了宵蛀。(注:好好理解這句話@ブ)
從上面這句可以看出,epoll的基礎(chǔ)就是回調(diào)呀糖埋!
-
如此宣吱,一顆紅黑樹,一張準(zhǔn)備就緒句柄鏈表瞳别,少量的內(nèi)核cache征候,就幫我們解決了大并發(fā)下的socket處理問題杭攻。執(zhí)行epoll_create時,創(chuàng)建了紅黑樹和就緒鏈表疤坝,執(zhí)行epoll_ctl時兆解,如果增加socket句柄,則檢查在紅黑樹中是否存在跑揉,存在立即返回锅睛,不存在則添加到樹干上,然后向內(nèi)核注冊回調(diào)函數(shù)历谍。當(dāng)網(wǎng)卡監(jiān)聽到socket數(shù)據(jù)變化時现拒,就查找該紅黑樹是否有監(jiān)聽對應(yīng)的socket(紅黑樹查找速度很快),找到紅黑樹中有該socket時引發(fā)中斷事件望侈,向準(zhǔn)備就緒鏈表中插入該socket句柄數(shù)據(jù)印蔬。執(zhí)行epoll_wait時立刻返回準(zhǔn)備就緒鏈表里的數(shù)據(jù)即可。
24.2脱衙、epoll的水平觸發(fā)和邊沿觸發(fā)侥猬?
-
水平觸發(fā)(LT):
當(dāng)epoll檢測到其上有事件發(fā)生并通知應(yīng)用程序時,應(yīng)用程序可以不立即處理捐韩,這樣當(dāng)應(yīng)用程序再次調(diào)用epoll中調(diào)用函數(shù)時退唠,epoll會再次通知應(yīng)用程序此事件,直到被處理。
邊沿觸發(fā)(ET):
當(dāng)epoll檢測到其上有事件發(fā)生并通知應(yīng)用程序時荤胁,應(yīng)用程序必須立即處理瞧预,并且下一次的epoll調(diào)用,不會再向應(yīng)用程序通知此事件仅政。
所以ET模式大大得降低了同一個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模式玖瘸〗斩铮可見ET,LT模式是針對具體的某個事件來設(shè)置的
select/poll會因為監(jiān)聽fd的數(shù)量而導(dǎo)致效率低下,因為它是輪詢所有fd雅倒,有數(shù)據(jù)就處理璃诀,沒數(shù)據(jù)就跳過,所以fd的數(shù)量會降低效率蔑匣;而epoll只處理就緒的fd劣欢,它有一個就緒設(shè)備的鏈表棕诵,每次只輪詢該鏈表的數(shù)據(jù),然后進行處理凿将。 - LT, ET這件事怎么做到的呢校套?當(dāng)一個socket句柄上有事件時,內(nèi)核會把該句柄插入上面所說的準(zhǔn)備就緒list鏈表牧抵,這時我們調(diào)用epoll_wait笛匙,會把準(zhǔn)備就緒的socket拷貝到用戶態(tài)內(nèi)存,然后清空準(zhǔn)備就緒list鏈表犀变,最后妹孙,epoll_wait干了件事,就是檢查這些socket获枝,如果不是ET模式(就是LT模式的句柄了)蠢正,并且這些socket上確實有未處理的事件時,又把該句柄放回到剛剛清空的準(zhǔn)備就緒鏈表了映琳。所以机隙,非ET的句柄,只要它上面還有事件萨西,epoll_wait每次都會返回這個句柄有鹿。(從上面這段,可以看出谎脯,LT還有個回放的過程葱跋,低效了)
25、Linux下的三個特殊進程
- Linux下有三個特殊的進程idle進程(PID=0)源梭,init進程(PID=1)娱俺,和kthreadd(PID=2)
- idle(空閑)進程由系統(tǒng)自動創(chuàng)建,運行在內(nèi)核態(tài)
idle進程其pid=0废麻,其前身是系統(tǒng)創(chuàng)建的第一個進程荠卷,也是唯一一個沒有通過fork或者kernel_thread產(chǎn)生的進程。完成加載系統(tǒng)后烛愧,演變?yōu)檫M程調(diào)度油宜、交換。 - kthreadd進程由idle通過kernel_thread創(chuàng)建怜姿,并始終運行在內(nèi)核空間慎冤,負(fù)責(zé)所有內(nèi)核進程的調(diào)度和管理。
它的任務(wù)就是管理和調(diào)度其他內(nèi)核線程kernel_thread, 會循環(huán)執(zhí)行一個kthread的函數(shù)沧卢,該函數(shù)的作用就是運行kthread_create_list全局鏈表中維護的kthread, 當(dāng)我們調(diào)用kernel_thread創(chuàng)建的內(nèi)核線程會被加入到此鏈表中蚁堤,因此所有的內(nèi)核線程都是直接或者間接的以kthreadd為父進程 。 - init進程由idle通過kernel_thread創(chuàng)建但狭,在內(nèi)核空間完成初始化后披诗,加載init程序
在這里我們就主要講解下init進程撬即,init進程由0進程創(chuàng)建,完成系統(tǒng)的初始化藤巢,是系統(tǒng)中所有其他用戶進程的祖先進程
Linux中的所有進程都是由init進程創(chuàng)建并運行的搞莺。首先Linux內(nèi)核啟動,然后在用戶空間中啟動init進程掂咒,再啟動其他系統(tǒng)進程才沧。在系統(tǒng)啟動完成后,init將變成為守護進程監(jiān)視系統(tǒng)其他進程绍刮。
所以說init進程是Linux系統(tǒng)操作中不可缺少的程序之一温圆,如果內(nèi)核找不到init進程就會試著運行/bin/sh,如果運行失敗孩革,系統(tǒng)的啟動也會失敗岁歉。
26、平衡二叉樹和紅黑樹的區(qū)別膝蜈?
- 平衡二叉樹AVL是指嚴(yán)格的平衡二叉樹——即每個樹結(jié)點的左右子樹的深度差值(平衡因子)不超過1锅移。
- 紅黑樹只是弱平衡二叉樹,不是完全遵守平衡因子不超過1的饱搏,也就是說可能會出現(xiàn)某結(jié)點左右子樹的深度差值大于1非剃,因此紅黑樹是相對平衡的二叉樹。
- 那為什么紅黑樹比AVL平衡二叉樹使用的地方更多呢推沸?
- 因為AVL為了保持嚴(yán)格平衡备绽,每次有數(shù)據(jù)插入或刪除后都需要做更多的檢查調(diào)整工作,效率較低鬓催。
而紅黑樹不需要維持嚴(yán)格平衡肺素,因此插入和刪除效率更高。 - 就查找效率來說宇驾,AVL因為嚴(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é)合薛匪,我們在寫程序時捐川,可以快速復(fù)用,而不需要重復(fù)的去造輪子逸尖,要站在巨人的肩膀上古沥,節(jié)約時間。
- 二是高效性娇跟。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)和算法原理,然后擴展輪子吃谣,或者是深入定制自己的輪子乞封。
- STL更注重的是模板泛型編程,而不是面向?qū)ο缶幊獭?/li>
2岗憋、STL六大組件之間的關(guān)系肃晚?
- 六大組件:空間配置器(allocator)、容器(container)澜驮、迭代器(iterator)陷揪、算法(algorithm)、仿函數(shù)(functor)[函數(shù)對象]杂穷、適配器(adapter)[包裝器]悍缠。
-
空間配置器
為容器分配內(nèi)存,容器的模板參數(shù)中都有一個默認(rèn)的空間配置器class Alloc = alloc
耐量。最簡單的空間配置器實現(xiàn)就是類內(nèi)部用alllocate成員函數(shù)封裝::operator new(相當(dāng)于malloc)分配內(nèi)存飞蚓,再用construct成員函數(shù)負(fù)責(zé)對象的構(gòu)造(實際上是借用定位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)趴拧,來實現(xiàn)對象的內(nèi)存分配和構(gòu)造初始化以及用完后析構(gòu)和釋放內(nèi)存。而這些分步也是實現(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:隨機存取迭代器,不僅可讀可寫哮笆,還支持順序存儲如用數(shù)組下標(biāo)的跳步移動訪問功能来颤。 算法
-
仿函數(shù)
又叫函數(shù)對象,常用類名加()代表一個臨時對象疟呐,可為算法動態(tài)地提供某種策略脚曾。類中一定重載了operator()。比如常用的將算法庫中的sort函數(shù)的比較策略用一個仿函數(shù)傳入启具,當(dāng)然也可傳入一個函數(shù)指針或是對類型重載<號本讥。一般函數(shù)指針可以當(dāng)做仿函數(shù)對象用。 -
適配器類
的內(nèi)部封裝(組合)了一個其他類的對象鲁冯。如容器適配器stack/queue默認(rèn)就是在內(nèi)部組合了一個deque<T> dq;
拷沸,該deque對象的生命周期和stack/queue對象一樣,然后通過封裝dq對象的某些成員函數(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)用場景進行選擇:
map/unordered_map 不允許重復(fù)元素
multimap/unordered_multimap允許重復(fù)元素
map/multimap底層基于紅黑樹巢寡,元素自動有序,且插入椰苟、刪除效率高
unordered_map/unordered_multimap底層基于哈希表抑月,故元素?zé)o序,查找效率高舆蝴。
4爪幻、type_traits<T>是什么作用菱皆?
- 這個的作用是對類型T進行一些和類型相關(guān)的詢問,然后返回true或false的相關(guān)回答(即對類型T萃取出相關(guān)類型說明)挨稿。也有iterator_traits可以萃取出一個迭代器的相關(guān)類型。
比如在空間配置器alloctor的實現(xiàn)中京痢,有個destroy成員函數(shù)在對數(shù)組中的所有成員析構(gòu)時奶甘,會先用type_traits<T>::has_trivial_destructor
檢查成員類型的析構(gòu)函數(shù)是否是trivial(不重要的),若是返回__false_type
則說明這個類型T的析構(gòu)函數(shù)是重要的祭椰,必須對數(shù)組中每個對象依次調(diào)用析構(gòu)函數(shù)臭家。相反,若是返回__true_type
說明確實是不重要的析構(gòu)函數(shù)(甚至是只有默認(rèn)隱藏的空析構(gòu)函數(shù))方淤,則不需要去遍歷數(shù)組對象調(diào)用析構(gòu)函數(shù)钉赁,可以大大提高效率。
5携茂、STL的空間配置器實現(xiàn)原理你踩?
- STL空間配置器的作用是維護對象的
內(nèi)存分配、釋放以及構(gòu)造讳苦、析構(gòu)
带膜。因此我們可以從這四個作用來看實現(xiàn)原理,空間配置器中一定實現(xiàn)了allocate/deallocate/contructor/destroy
這四個成員函數(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
這幾個C語言函數(shù)從堆中分配釋放內(nèi)存給用戶穗慕,并且自己寫了處理內(nèi)存不足的函數(shù)set_malloc_handle()
饿敲,大致思想就是用一個無限循環(huán)來不斷嘗試分配內(nèi)存(可能程序在剛開始時就預(yù)先分配了大塊內(nèi)存?zhèn)溆茫@時候就可以釋放一部分出來解決內(nèi)存分配不足問題)逛绵,直到分配成功怀各。如果不寫這個處理函數(shù),那就只能返回內(nèi)存分配不足的異呈趵耍或直接退出程序瓢对。 - 第二級配置器(又叫次層配置sub-allocation)是維護了一個內(nèi)存池,處理頻繁的小塊內(nèi)存的申請胰苏,相比第一級從堆中申請不僅速度更快還更有線程安全性硕蛹,也能更好地處理內(nèi)存碎片的問題節(jié)約內(nèi)存。內(nèi)存池用一個數(shù)組
free_list[16]
維護了128/8=16個鏈表,分別存放區(qū)塊大小為{8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128}法焰。首次從對應(yīng)鏈表中比如16申請區(qū)塊時秧荆,會調(diào)用refill(n=16)函數(shù)
先分配20個區(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那個free_list[16/8-1]
的那個鏈表中獲取一個元素(區(qū)塊)卵蛉。鏈表中的元素(區(qū)塊)obj也有意思颁股,是一個union聯(lián)合體,在分配給用戶使用前將obj->free_list_link賦值給對應(yīng)鏈表的首元素my_free_list(由于該變量是obj * volatile * 類型
傻丝,直接指向了實際內(nèi)存而不是存放在臨時寄存器中甘有,因此后面給它重新復(fù)制會被立即修改,而不用擔(dān)心多線程問題)葡缰,以便下次再從這個鏈表申請內(nèi)存時可以立即獲取一個空閑內(nèi)存塊亏掀。然后再分配給用戶使用,從第一個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; //保存著下一個空閑區(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個鏈表對應(yīng)的那個。
-
內(nèi)存池設(shè)計:
兩根指針start_free和end_free分別代表池中自由的內(nèi)存的首字節(jié)和尾字節(jié)地址胰丁;將要申請的字節(jié)數(shù)擴展到8的倍數(shù)的函數(shù)随橘;一個指針數(shù)組free_list維護16個鏈表的區(qū)塊的首地址;一個union obj表示一個區(qū)塊锦庸,內(nèi)含下一個區(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)換成多個小區(qū)快(最多20塊)并循環(huán)鏈接起來扬卷;chunk_alloc()負(fù)責(zé)提供大塊的區(qū)塊牙言,管理內(nèi)存池中剩余的區(qū)塊,不夠時從堆空間獲取兩倍多的需求填充內(nèi)存池怪得;deallocate頭插法歸還內(nèi)存到對應(yīng)鏈表的首部咱枉。
6卑硫、STL中vector內(nèi)存動態(tài)擴展的倍數(shù)是多少?設(shè)為多少比較合適蚕断?為什么
- 不同的STL庫實現(xiàn)欢伏,可能會設(shè)置為不同的倍數(shù),大都是設(shè)為2或者1.5倍亿乳。理論上來講設(shè)為1.5倍更好颜懊,更有利于后面擴展內(nèi)存時復(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