new与malloc的区别以及实现方法
版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/shanghairuoxiao/article/details/70337890
new和malloc的內存分配在哪
分配在堆上。也有說new是分配在自由存儲區而malloc分配在堆上,自由存儲區可以是堆也可以不是,具體要看new內部的實現。操作系統在堆上維護一個空閑內存鏈表,當需要分配內存的時候,就查找這個表,找到一塊內存大于所需內存的區域,分配內存并將剩余的內存空間返還到空閑鏈表上(如果有剩余的話)。
new/delete和malloc/free的區別
1. malloc和free是庫函數,而new和delete是C++操作符;
2. new自己計算需要的空間大小,比如’int * a = new,malloc需要指定大小,例如’int * a = malloc(sizeof(int))’;
3. new在動態分配內存的時候可以初始化對象,調用其構造函數,delete在釋放內存時調用對象的析構函數。而malloc只分配一段給定大小的內存,并返回該內存首地址指針,如果失敗,返回NULL。
4. new是C++操作符,是關鍵字,而operate new是C++庫函數
5. opeartor new /operator delete可以重載,而malloc不行
6. new可以調用malloc來實現,但是malloc不能調用new來實現
7. 對于數據C++定義new[]專門進行動態數組分配,用delete[]進行銷毀。new[]會一次分配內存,然后多次調用構造函數;delete[]會先多次調用析構函數,然后一次性釋放。
分配數組不同之處 int char* pa = new char[100]; int char* pb = malloc(sizeof(char) * 100);- 1
- 2
- 3
8. malloc能夠直觀地重新分配內存
使用malloc分配的內存后,如果在使用過程中發現內存不足,可以使用realloc函數進行內存重新分配實現內存的擴充。realloc先判斷當前的指針所指內存是否有足夠的連續空間,如果有,原地擴大可分配的內存地址,并且返回原來的地址指針;如果空間不夠,先按照新指定的大小分配空間,將原有數據從頭到尾拷貝到新分配的內存區域,而后釋放原來的內存區域。?
new沒有這樣直觀的配套設施來擴充內存。
new和malloc內部實現的區別
new
以下是從網上找來的一段關于new的代碼,不知道和標準的實現是否有區別,但是原理應該是這樣,足夠來說明問題了:
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) { // try to allocate size bytes void *p; while ((p = malloc(size)) == 0) if (_callnewh(size) == 0) { // report no memory _THROW_NCEE(_XSTD bad_alloc, );} return (p); }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
new: 可以理解成兩步:?
1. 調用operate new()分配內存,如果內存不足失敗,拋出異常;?
2. 如果需要的話,在那段內存上初始化對象(賦值或者調用構造函數),這個應該是由編譯器根據代碼來控制的。
因此對于new和malloc檢查是否正確分配的方法是不一樣的
int *a = (int *)malloc ( sizeof (int )); if(NULL == a) {... } else {... } 從C語言走入C++陣營的新手可能會把這個習慣帶入C++:int * a = new int(); if(NULL == a) {... } else { ... } 實際上這樣做一點意義也沒有,因為new根本不會返回NULL,而且程序能夠執行到if語句已經說明內存分配成功了,如果失敗早就拋異常了,后面的代碼就不會執行了。正確的做法應該是使用異常機制:try {int *a = new int(); } catch (std::bad_alloc& e) {... }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
為了照顧原來習慣的程序員,C++可以通過nothrow關鍵字來實現new不拋異常而是返回NULL。
int* p = new(std::nothrow) int;- 1
malloc
參考網上多篇文章,自己寫了一個malloc,也沒測過,如有錯誤歡迎指正。但是,用來說明malloc的原理的我想是沒問題的。
#define malloc_addr 0x00000 #define malloc_size 0x22222#ifndef NULL #define NULL 0 #endifvoid* managed_memory_start = NULL; //堆區的起始地址 void* managed_memory_end = NULL; //堆取的終止地址 int is_initialized = 0;/** 內存控制塊,通過內存控制塊將堆區的內存用雙向鏈表連接起來管理*/ typedef struct {unsigned int is_available;unsigned int current_block_size;unsigned int prev_block_size; }mem_control_block;void malloc_init(void) {mem_control_block* tmp = NULL;managed_memory_start = (void*)malloc_addr;managed_memory_end = (void*)(malloc_addr + malloc_size);tmp = (mem_control_block*)managed_memory_start;tmp->is_available = 1;tmp->current_block_size = (managed_memory_end - managed_memory_start) - sizeof(mem_control_block);tmp->prev_block_size = 0;is_initialized = 1; }void* malloc(size_t size) {//初始化,最開始鏈表只有一個節點if(!is_initialized){malloc_init();}//保存內存地址游標void* current_location = NULL;//保存當前內存控制塊的位置mem_control_block* current_location_mcb = NULL;//如果塊太大,取下所需大小,剩余放回鏈表mem_control_block* leave_location_mcb = NULL;//定義一個用于返回的指針,返回的地址是控制塊加上前面結構體的大小void* memory_location = NULL;//把游標指向堆的首地址current_location = managed_memory_start;while(current_location <= managed_memory_end){current_location_mcb = (mem_control_block*)current_location;//判斷該節點是否被空閑if(current_location_mcb->is_available){//判斷該空閑塊大于需要,但是剩下的空間又不足以維持一個空閑塊,就把整個塊都分配了,為了維持鏈表的連續性//實際上也浪費不了多少內存,因為一個struct結構體很小if(current_location_mcb->current_block_size < size + 2 * sizeof(mem_control_block)){current_location_mcb->is_available = 0;break;}else //如果空閑塊大于需要的,就把剩余的塊放入leave_location_mcb節點了{unsigned int process_blocksize;//另當前塊為被使用狀態current_location_mcb->is_available = 0;process_blocksize = current_location_mcb->current_block_size;current_location_mcb->current_block_size = size + sizeof(mem_control_block);leave_location_mcb = (mem_control_block*)(current_location + process_blocksize);leave_location_mcb->is_available = 1;//當前塊大小減去需要的內存減去一個控制塊結構體就是剩余的大小leave_location_mcb->current_block_size = process_blocksize - sizeof(mem_control_block) - size;//leaveo塊的前一個塊的大小就是要分配的大小leave_location_mcb->prev_block_size = size + sizeof(mem_control_block);}}//如果該塊不空閑,則指針游標指向下一塊的首地址current_location += current_location_mcb->current_block_size;}//如果空閑鏈表中已經沒有合適的塊就擴大堆區的范圍if(!memory_location){//申請取擴大堆取的內存if(sbrk(size + sizeof(mem_control_block)) != -1)return NULL;//如果空閑鏈表中沒有合適的塊,那么必然是遍歷了整個鏈表,此時的current_location_mcb指向原來空閑鏈表的最后一個塊//將該塊的大小保存下來,就是馬上要分配塊的前一個塊的大小了unsigned int prev_size = current_location_mcb->current_block_size;//之前的循環會使得current_location指向最后一個塊末尾的下一個地址current_location_mcb = (mem_control_block*)current_location;current_location_mcb->current_block_size = size + sizeof(mem_control_block);current_location_mcb->prev_block_size = prev_size;}memory_location = current_location + sizeof(mem_control_block);return memory_location; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
系統為堆區保存了兩個指針一個指向堆的首地址,一個指向堆的尾地址(我猜想這兩個指針是不是就是有vm_area_struct里的兩個指針維護的,源碼還沒看到只是猜測)。系統通過結構體mem_control_block將整個堆區分成一個個塊,每個塊都已這樣一個結構體開頭,結構體里維護了塊的大小。malloc分配內存的時候,從第一個塊開始遍歷,如果找到了塊已經被使用,那么就找下一個塊,如果找到的塊比需要的小繼續找下一個塊,直到找到比我需要大于等于的塊,然后將該塊mem_control_block中的標志位設置為被使用了,如果塊有剩余的就將剩余的空間添加一個mem_control_block變成一個新塊。如果遍歷了整個堆空間都沒有找到合適的塊,那么就調用sbrk函數擴大堆的范圍。
brk() and sbrk() change the location of the?program break, which defines the end of the process’s data segment (i.e., the program break is the first location after the end of the uninitialized data segment). Increasing the program break has the effect of allocating memory to the process; decreasing the break deallocates memory.
brk() sets the end of the data segment to the value specified by addr, when that value is reasonable, the system has enough memory, and the process does not exceed its maxi‐mum data size (see setrlimit(2)).
sbrk()increments the program’s data space by increment bytes. Calling sbrk() with an increment of 0 can be used to find the current location of the program break.
上面這段摘自Linux手冊,brk函數和sbrk都是擴大堆區(program break就是堆,因為文中說了program break就是bss后的第一個位置)尾地址的。只不過brk通過指定一個地址,而sbrk通過追加一個大小。
通過上面的代碼我們可以直到當我們調用malloc分配n個字節的空間時,實際上占用了n + sizeof(mem_control_block)個字節的空間。但是返回的地址是緊跟結構體mem_control_block后面的第一個地址。那么相信聰明的你已經猜到了free是如何釋放內存的了。沒錯,就是把指針往回倒個sizeof(mem_control_block)的距離,然后設置標志位為可用。為了減少內存碎片,會取查找前面一個塊和后面一個塊,如果有空閑塊就合并為一個。
給free()中的參數就可以完成釋放工作!這里要追蹤到malloc()的申請問題了。申請的時候實際上占用的內存要比申請的大。因為超出的空間是用來記錄對這塊內存的管理信息。先看一下在《UNIX環境高級編程》中第七章的一段話:
大多數實現所分配的存儲空間比所要求的要稍大一些,額外的空間用來記錄管理信息——分配塊的長度,指向下一個分配塊的指針等等。這就意味著如果寫過一個已分配區的尾端,則會改寫后一塊的管理信息。這種類型的錯誤是災難性的,但是因為這種錯誤不會很快就暴露出來,所以也就很難發現。將指向分配塊的指針向后移動也可能會改寫本塊的管理信息。
參考:
淺談 C++ 中的 new/delete
文章1
文章2
文章3
如何實現一個malloc
總結
以上是生活随笔為你收集整理的new与malloc的区别以及实现方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言面向对象编程(六):配置文件解析
- 下一篇: C语言:函数中参数的传值与传地址