您好,登錄后才能下訂單哦!
如何理解C語言中的動態內存分配,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
在一般的程序中,我們難免會遇到動態的申請內存,那么動態內存分配的意義到底是什么呢?在 C 語言中的一切操作都是基于內存的,變量和數組都是內存的別名。內存分配由編譯器在編譯期間決定,定義數組的時候必須指定數組長度,數組長度當然也是在編譯期就必須確定的。
那么為什么會有動態分配內存的需求呢?在程序運行的過程中,可能需要使用一些額外的內存空間。我們都是在 C 語言中使用 malloc 來動態申請內存的,當時釋放的時候是用 free,下來我們看看 malloc 和 free 用于執行動態內存分配和釋放時是怎樣進行的,用下圖進行說明
我們可以看到程序通過 malloc 在內存池中進行申請,那么歸還則是通過 free 進行釋放的。如果我們只是 malloc 進行申請而不 free,那么我們的內存池將會被用完,那么程序也就崩潰了。這就是我們平時所說的內存泄漏。
malloc 所分配的是一塊連續的內存,它是以字節為單位并且不帶任何的類型信息。free 則是用于將動態內存歸還系統。這兩個函數的原型是 void* malloc(size_t size); void free(void* pointer);我們得注意這么幾點:a> malloc 和 free 是庫函數,而不是系統調用;b> malloc 實際分配的內存可能會比請求的多;c> 不能依賴于不同平臺下的 malloc 行為;d> 當請求的動態內存無法滿足時,malloc 返回 NULL;e> 當 free 的參數為 NULL時,函數直接返回。
那么我們接下來思考下,malloc(0) 將返回什么?是會報錯?還是啥也不做?還是會出現不確定的結果?我們做下實驗看看
#include <stdio.h> int main() { int* p = (int*)malloc(0); printf("p = %p\n", p); return 0; }
我們看看編譯結果
我們看到編譯器給出警告了,但是還是成功執行了。其實我們平時所說的內存有兩個概念,一個是它的起始地址,一個是大小。在這塊我們就好解釋了,malloc(0) 只是申請的內存大小為0而已,但是它還會有起始地址。所以如果當我們在程序中無限次的 malloc(0) 時,程序最終會崩潰,因為它的地址信息也會占用空間。
下來我們再看一個代碼,是唐長老從實際工程中抽象出來的內存檢測模塊
test.c 源碼
#include <stdio.h> #include "mleak.h" void f() { MALLOC(100); } int main() { int* p = (int*)MALLOC(3 * sizeof(int)); f(); p[0] = 1; p[1] = 2; p[2] = 3; FREE(p); PRINT_LEAK_INFO(); return 0; }
mleak.h 源碼
#ifndef _MLEAK_H_ #define _MLEAK_H_ #include <malloc.h> #define MALLOC(n) mallocEx(n, __FILE__, __LINE__) #define FREE(p) freeEx(p) void* mallocEx(size_t n, const char* file, const line); void freeEx(void* p); void PRINT_LEAK_INFO(); #endif
mleak.c 源碼
#include "mleak.h" #define SIZE 256 /* 動態內存申請參數結構體 */ typedef struct { void* pointer; int size; const char* file; int line; } MItem; static MItem g_record[SIZE]; /* 記錄動態內存申請的操作 */ void* mallocEx(size_t n, const char* file, const line) { void* ret = malloc(n); /* 動態內存申請 */ if( ret != NULL ) { int i = 0; /* 遍歷全局數組,記錄此次操作 */ for(i=0; i<SIZE; i++) { /* 查找位置 */ if( g_record[i].pointer == NULL ) { g_record[i].pointer = ret; g_record[i].size = n; g_record[i].file = file; g_record[i].line = line; break; } } } return ret; } void freeEx(void* p) { if( p != NULL ) { int i = 0; /* 遍歷全局數組,釋放內存空間,并清除操作記錄 */ for(i=0; i<SIZE; i++) { if( g_record[i].pointer == p ) { g_record[i].pointer = NULL; g_record[i].size = 0; g_record[i].file = NULL; g_record[i].line = 0; free(p); break; } } } } void PRINT_LEAK_INFO() { int i = 0; printf("Potential Memory Leak Info:\n"); /* 遍歷全局數組,打印未釋放的空間記錄 */ for(i=0; i<SIZE; i++) { if( g_record[i].pointer != NULL ) { printf("Address: %p, size:%d, Location: %s:%d\n", g_record[i].pointer, g_record[i].size, g_record[i].file, g_record[i].line); } } }
我們看到在 test.c 中第6行 f() 函數中動態申請了內存,但是沒有進行釋放。由于是局部的,當這個函數調用完后,將產生內存泄漏。那么我們在第 21 行將會打印出信息。我們這個對應的函數是怎么實現的呢,在 mleak.c 中將申請得到的內存地址放入一個數組中,在后面會進行檢查,如果進行 FREE 操作,便會在數組中對應的刪除標記,否則標記存在。如果標記存在,我們則會打印出對應的信息來。我們來看看編譯結果
我們看到在地址為 0x9d13018 處存在100大小的內存沒進行釋放,它位于 test.c 的第6行。下來我們注釋掉 teat.c 中的第19行,看看這個內存沒進行釋放是否會打印出來
我們看到一樣的打印出來了。證明我們這個內存泄漏的檢測模塊還是很準的。
下來我們在來看看 calloc 和 realloc,它們是 malloc 的同胞兄弟,原型分別為:void* calloc(size_t num, size_t size); void* realloc(void* pointer, size_t new_size);那么 calloc 的參數代表所返回內存的類型信息,其中 calloc 會將返回的內存初始化為 0;realloc 用于修改一個原先已經分配 的內存塊大小,在使用 realloc 之后應該使用其返回值,當 pointer 的第一個參數為 NULL 時,等價于 malloc。
下來我們以代碼為例進行分析
#include <stdio.h> #include <malloc.h> #define SIZE 5 int main() { int i = 0; int* pI = (int*)malloc(SIZE * sizeof(int)); short* pS = (short*)calloc(SIZE, sizeof(short)); for(i=0; i<SIZE; i++) { printf("pI[%d] = %d, pS[%d] = %d\n", i, pI[i], i, pS[i]); } printf("Before: pI = %p\n", pI); pI = (int*)realloc(pI, 2 * SIZE * sizeof(int)); printf("After: pI = %p\n", pI); for(i=0; i<10; i++) { printf("pI[%d] = %d\n", i, pI[i]); } free(pI); free(pS); return 0; }
我們看看編譯結果
按照我們前面講的,數組 pI 中的數應該是隨機數的,數組 pS 的數是被初始化為 0 的。可是現在全是0,別著急,這只是 gcc 中做的優化。我們看到數組 pI 在調用 realloc 后的大小確實改變了,并且地址也變了。下來我們看看 BCC 編譯器中是怎樣的
我們看到數組 pI 中的數確實是隨機數了,而數組 pS 中的數依舊全是 0。
總結如下:
1、動態內存分配是 C 語言中的強大功能,程序能夠在需要的時候有機會使用更多的內存;
2、malloc 單純的從系統中申請固定字節大小的內存,calloc 能以類型大小為單位申請內存并初始化為0,realloc 用于重置內存大小。
看完上述內容,你們掌握如何理解C語言中的動態內存分配的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。