雙宿雙飛的 malloc 和 free
這兩個函式是一對好朋友,幾乎是形影不離。有 malloc
的地方就應該有 free
的存在。
今天跟大家聊聊 malloc
和 free
這對好基友,這兩個函式都是對堆記憶體進行管理的函式,另外還有 calloc
、 realloc
、 reallocf
、 valloc
等堆記憶體管理函式。
void *
在進行下面話題之前,我們先回憶一下 void *
是什麼?
void *
表示未確定型別的指標。C/C++規定, void *
型別可以強制轉換為任何其它型別的指標。
void *
也被稱之為無型別指標, void *
可以指向任意型別的資料,就是說可以用任意型別的指標對 void *
賦值,如下示例:
void *p1; int *p2; p1 = p2;
但一般不會反過來使用,如下示例在有些編譯器上面可以編譯通過,有些就不行:
void *p1; int *p2; p2 = p1;
可以修改一下程式碼,將 void *
轉換為對應的指標型別再進行賦值,如下示例:
void *p1; int *p2; p2 = (char *)p1;
由於 GNU 和 ANSI 對 void *
型別指標參與運算的規定不一樣,所以為了相容二者並且讓程式有更好的相容性,最好還是將 void *
轉換為有明確型別的指標再參與運算,如下示例。
void *pd; char *pc = (char *)pd; pc ++; pc += 1;
malloc
函式原型:
void * malloc(size_t size);
malloc
向系統申請分配指定 size
個位元組的記憶體空間,即 malloc
函式用來從堆空間中申請指定的 size
個位元組的記憶體大小,返回型別是 void *
型別,如果成功,就會返回指向申請分配的記憶體,否則返回空指標,所以 malloc
不保證一定成功。
檢視函式手冊或者直接在 linux、macOS 上面直接 man malloc
會顯示對應的函式資訊:
The malloc() function allocates size bytes of memory and returns a pointer to the allocated memory. If successful, malloc() function return a pointer to allocated memory. If there is an error, they return a NULL pointer and set errno to ENOMEM.
另外需要注意一個問題,使用 malloc
函式分配記憶體空間成功後, malloc
不會對資料進行初始化,裡邊資料是隨機的垃圾資料,所以一般結合 memset
函式和 malloc
函式 一起使用。
int *arr; arr = (int *)malloc(10 * sizeof(int)); if (NULL != arr) { memset(arr, 0, 10 * sizeof(int)); printf("arr: %p\n", arr); }
char *arr; arr = (char *)malloc(10 * sizeof(char)); if (NULL != arr) { memset(arr, '\0', 10 * sizeof(char)); printf("arr string: %s\n", arr); }
為了安全起見,建議可以考慮使用 calloc()
函式,後面會提到它。
函式 free
、 malloc
、 calloc()
都被包含在 stdlib.h
檔案中。
free
函式原型:
void free(void *ptr);
我們知道在 C 語言中, 堆上的記憶體空間不會自動釋放(Java 有自動回收機制,而 C 語言沒有),直到呼叫 free
函式,才會釋放堆上的儲存空間,即 free
函式會釋放指標指向的記憶體分配空間。
下面是函式手冊查到關於 free
函式的資料:
The free() function deallocates the memory allocation pointed to by ptr. If ptr is a NULL pointer, no operation is performed.
對於 free
函式我們要走出一個誤區,不要以為呼叫了 free
函式,變數就變為 NULL
值了。本質是 free
函式只是割斷了指標所指的申請的那塊記憶體之間的關係,並沒有改變所指的地址(本身儲存的地址並沒有改變)。如下示例:
char *pchar = (char *)malloc(10 * sizeof(char)); if (NULL != pchar) { strcpy(pchar, "blog"); /* pchar所指的記憶體被釋放,但是pchar所指的地址仍然不變 */ free(pchar); /* 該判斷沒有起到防錯作用,此時 pchar 並不為 NULL */ if (NULL != pchar) { strcpy(pchar, "it"); printf("pchar: %s", pchar); } }
正確且安全的做法是對指標變數先進行 free
然後再將其值置為 NULL
,如下下面示例:
char *pchar = (char *)malloc(10 * sizeof(char)); if (NULL != pchar) { strcpy(pchar, "blog"); /* pchar所指的記憶體被釋放,但是pchar所指的地址仍然不變 */ free(pchar); /* 將其置為 NULL 值 */ pchar = NULL; /* 該判斷沒有起到防錯作用,此時 pchar 並不為 NULL */ if (NULL != pchar) { strcpy(pchar, "it"); printf("pchar: %s", pchar); } }
malloc、free 小結
1、連續記憶體塊
malloc
函式申請的是連續的一塊記憶體,如果所申請的記憶體塊大於目前堆上剩餘記憶體塊,則記憶體分配會失敗,函式返回 NULL
值。
注意:上面說的 堆上剩餘記憶體塊
不是所有剩餘記憶體塊之和,而是連續的記憶體。
2、雙宿雙飛才好
呼叫 malloc
函式多餘 free
函式會發生記憶體洩漏,這個很好理解,因為申請過的記憶體沒有被釋放完。呼叫 malloc
函式少於 free
函式,肯定會出錯。換句話說,在程式中 malloc
的使用次數務必要和 free
相等,否則必有隱患或者發生錯誤。
如下面的例子 free
兩次指標變數就會在執行時報錯: malloc: *** error for object 0x10071be90: pointer being freed was not allocated
char *pchar = (char *)malloc(10 * sizeof(char)); free(pchar); free(pchar);
對指標變數進行 free
之後,一定要記得對其賦值為 NULL
,否則該指標就是一個野指標,這個在上面已經說明。
3、0位元組的記憶體有毒
使用 malloc
函式也可以申請0位元組的記憶體,該函式的返回值並不是 NULL
,而是返回一個正常的記憶體地址,所以如果使用這種方式申請的記憶體很危險,如下面的例子,指標 pchar
是一個使用 malloc
函式建立的佔用0位元組的記憶體空間的一個指標變數, if (NULL == pchar)
並沒有生效,而是執行了 else
語句中的程式碼,執行到 strcpy(pchar, "blog")
就直接崩潰了。
char *pchar = (char *)malloc(0); if (NULL == pchar) { printf("malloc 0 byte memory failed.\n"); } else { printf("malloc 0 byte successfully and pchar: %s.\n", pchar); pchar = "veryitman"; strcpy(pchar, "blog"); printf("pchar: %s.\n", pchar); }
calloc、realloc、reallocf、valloc
1、calloc 函式
void * calloc(size_t count, size_t size);
在堆上,分配 n*size
個位元組,並初始化為0,返回 void *
型別,返回值情況跟 malloc
一致。
函式 malloc()
和函式 calloc()
的主要區別是前者不能初始化所分配的記憶體空間,而後者能。如果由 malloc()
函式分配的記憶體空間原來沒有被使用過,則其中的每一位可能都是0;反之,如果這部分記憶體曾經被分配過,則其中可能遺留有各種各樣的資料。也就是說,使用 malloc()
函式的程式開始時(記憶體空間還沒有被重新分配)能正常進行,但經過一段時間(記憶體空間還已經被重新分配)可能會出現問題。
函式 calloc()
會將所分配的記憶體空間中的每一位都初始化為零,也就是說,如果你是為字元型別或整數型別的元素分配記憶體,那麼這些元素將保證會被初始化為0;如果你是為指標型別的元素分配記憶體,那麼這些元素通常會被初始化為空指標;如果你為實型資料分配記憶體,則這些元素會被初始化為浮點型的零。
The calloc() function contiguously allocates enough space for count objects that are size bytes of memory each and returns a pointer to the allocated memory. The allocated memory is filled with bytes of value zero.
2、realloc() 函式
void * realloc(void *ptr, size_t size);
重新分配堆上的 void指標
所指的空間為 size
個位元組,同時會複製原有內容到新分配的堆上儲存空間。
注意,若原來的 void指標
在堆上的空間不大於 size
個位元組,則保持不變。
The realloc() function tries to change the size of the allocation pointed to by ptr to size, and returns ptr. If there is not enough room to enlarge the memory allocation pointed to by ptr, realloc() creates a new allocation, copies as much of the old data pointed to by ptr as will fit to the new allocation, frees the old allocation, and returns a pointer to the allocated memory. If ptr is NULL, realloc() is identical to a call to malloc() for size bytes. If size is zero and ptr is not NULL, a new, minimum sized object is allocated and the original object is freed. When extending a region allocated with calloc(3), realloc(3) does not guarantee that the additional memory is also zero-filled.
3、reallocf() 函式
void * reallocf(void *ptr, size_t size);
reallocf()
函式是由 FreeBSD 實現的,它會在任何情況下釋放輸入的指標(即使是再分配失敗之後)。 reallocf()
一樣會呼叫 realloc
函式,但是隻有我們在獲得空的指標之後才會呼叫 free
函式。
下面是 reallocf
函式具體的實現部分:
void * reallocf(void *p, size_t size) { void *ptr = realloc(p, size); if (!p) { free(p); } return ptr; }
The reallocf() function is identical to the realloc() function, except that it will free the passed pointer when the requested memory cannot be allocated. This is a FreeBSD specific API designed to ease the problems with traditional coding styles for realloc causing memory leaks in libraries.
4、valloc() 函式
void * valloc(size_t size);
這個函式是最少見也是最少用的一個函式。
malloc
或 realloc
返回的是以8位元組對齊的記憶體地址,在64bits上是16位元組對齊。然而 memalign
或 valloc
可以更大的粒度進行位元組對齊。
valloc
是一個廢棄的函式,分配 size
大小的位元組,返回已分配的記憶體地址指標,其記憶體地址將是頁大小(page size)的倍數,如果分配失敗返回 NULL
。
The valloc() function allocates size bytes of memory and returns a pointer to the allocated memory. The allocated memory is aligned on a page boundary.
鋤禾日當午,汗滴禾下土,五一節快樂~