C语言程序设计 | 动态内存管理:动态内存函数介绍,常见的动态内存错误,柔性数组
動態內存管理目錄:
- 動態內存函數的介紹
- 常見的動態內存函數的錯誤
- 柔性數組
為什么會有動態內存管理呢
我們在日常使用中,創建一個數組,一個變量時都會開辟空間
如:
但是上面這種開辟空間的方法都具有一個特點
1. 空間開辟的大小是固定的,無法修改
2. 聲明數組的時候必須指定長度
但是當我們在實際使用中,因為適用不同的情況,我們的空間應該是可以隨時修改來應對所有需求的,但是上面的那種方法是不能實現的,所以我們就需要動態內存管理了。
動態內存函數的介紹
malloc和free
void* malloc (size_t size);這個函數向內存申請一塊連續可用的空間,并返回指向這塊空間的指針。
- 如果開辟成功,則返回一個指向開辟好空間的指針
- 如果開辟失敗,則放回一個NULL指針,因此malloc的返回值一定要做檢查
- 返回值的類型是 void* ,所以malloc函數并不知道開辟空間的類型,具體在使用的時候使用者自己來決定
- 如果參數size為0,malloc的行為是否是未定義的,取決于編譯器
在我們開辟一段空間后,我們也必須要將它釋放掉,而C語言同樣有這個函數
void free(void* ptr);free函數用來釋放動態開辟的內存
- 如果參數ptr指向的空間不是動態開辟的,那free函數的行為是未定義的。
- 如果參數ptr是NULL指針,則函數什么都不做
但是,free函數并不是完全銷毀這段空間,而是將這段空間的使用權收回,它可能會分配給其他東西,也可能一直放在那里不用,如果打個比方的話:我們申請一個空間,就相當于是拿到了一間房子的鑰匙,等到我們使用權結束后,我們并沒有把房子給銷毀,而是歸還房子的鑰匙,但是在歸還鑰匙之前,我們還是能夠進入這個房間,所以如果僅僅使用free,可能還不夠安全,所以應該采用下面的形式
int* arr = (int*)malloc(sizeof(int) * 5);free(arr);arr = NULL;calloc
void* calloc (size_t num, size_t size);- 函數的功能是為num個大小為size的元素開辟一段空間,并把空間中的所有字節初始化為0
- 與malloc基本一樣,只是calloc會將空間的所有字節初始化為0
在上面有提到過,我們之所以選擇動態內存管理,就是因為它可以讓我們隨心所欲的更改我們需要的空間的大小,那這個時候,我們就要用到realloc這個函數
realloc
void* realloc (void* ptr, size_t size);- ptr是要調整的內存地址
- size是調整后的大小
- 返回值為調整之后的內存起始地址
- 這個函數在調整原內存空間大小的基礎上,還會將原來內存中的數據移動到新的空間
但是這個函數在使用的時候會有兩種情況
原有空間之后有足夠大的空間
當情況1時,我們拓展空間時會直接在原有空間后面追加一段空間
原有空間之后沒有足夠大的空間
當情況2時,因為后面的空間無法存放,我們就需要在堆上面找到一個足夠的,連續的空間,將我們之前的數據拷貝過去,并且返回新的空間的地址
常見的動態內存錯誤
因為當我們內存申請失敗的時候,malloc函數會返回一個NULL指針,如果我們對NULL指針進行解引用操作,就會產生錯誤,所以我們應該對malloc的返回值進行檢查,確保內存申請成功。
2 對非動態開辟內存使用free釋放
void test() {int a = 10;int* p = &a;free(p); }對于這段函數,我們在編譯運行時可能不會報錯,但是我們在進行調試的時候就可以看到錯誤。
因為free函數不能對非動態開辟的內存進行操作
3.使用free釋放一塊動態開辟內存的一部分
void test() {int* p = (int*)malloc(100);p++;free(p); }這段代碼也是,在編譯的時候沒有任何報錯,但是在運行的時候就產生了錯誤。因為我們讓指針往前移動,指向的不再是動態內存的起始位置,但是free函數必須要對動態內存的起始地址進行操作,而我們的指針p已經不再指向動態內存的起始地址了,所以產生了錯誤
4.對同一塊動態內存多次釋放
void test() {int* p = (int*)malloc(100);free(p);free(p); }
我們重復free的時候,在編譯階段只會給出一個警告,但是在運行的時候就會報錯。警告的提示是使用未初始化的內存p,因為我們第一次free的時候已經將p給釋放了,所以重復free就會產生這樣的結果
拓展
void GetMemory(char* p) {p = (char*)malloc(100); } void test() {char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str); }這段代碼,我們乍一看是沒有錯誤的,編譯也沒有報錯沒有警告,但是在我們運行的時候就完全沒有作用。
要了解這一點,我們就首先得知道棧幀的概念。
當我們在調用一個函數的時候,我們會在棧上開辟一個棧幀,然后再函數結束的時候,釋放這個棧幀,而這個GetMemory調用時我們傳入的p就是在這個棧幀中作用,如果我們想讓它真的操作這個p,就應該傳這個p的地址,即一個二級指針
修改后:
void GetMemory(char** p) {*p = (char*)malloc(100); } void test() {char* str = NULL;GetMemory(&str);strcpy(str, "hello world");printf(str); }
這樣修改后就可以成功了
這里是可以運行的,這里就是我剛剛講解的將str置為空指針的問題,因為我們free了之后這個指針就變成了一個野指針,它可能可以訪問原來的空間,也有可能不行。
這里就是考察我們對于指針和數組的理解,在之前的指針那一章節中我就提到過
當我們在一個棧幀中,如果是str1的形式,我們返回的其實是在常量區中這段字符串的地址,而str2的方式就是其在這個棧幀中開辟的這段內存的地址,但隨著棧幀的銷毀,所以我們返回的其實是一段亂碼
這樣才能得到我們想要的答案
柔性數組
C語言中還存在這樣一個鮮為人知的東西,在一個結構中,最后一個允許是位置大小的數組,這就叫做柔性數組成員,它的使用如下
typedef struct st_type {int i;int a[0]; //柔性數組成員 有些編譯器不能在下標中寫入0 }type_a;- 結構中的柔性數組成員前面必須有至少一個其他成員
- sizeof返回的這種結構大小不包括柔性數組的內存
- 包含柔性數組成員的結構用malloc函數進行內存的動態分配,并且分配的內存應該大于結構的大小,以適應柔性數組的預期大小
柔性數組的使用
與結構同時分配內存
int main() {int i = 0;type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));p->i = 100;for (i = 0; i < 100; i++){p->a[i] = i;}free(p); }先給結構體分配空間,再給柔性數組分配空間
typedef struct st_type {int i;int *a; //柔性數組成員 有些編譯器不能在下標中寫入0 }type_a; int main() {int i = 0;type_a* p = (type_a*)malloc(sizeof(type_a));p->i = 100;p->a = (int*)malloc(p->i * sizeof(int));for (i = 0; i < 100; i++){p->a[i] = i;}free(p->a);p->a = NULL;free(p);p = NULL; }兩段代碼作用相同,但是第一個只需要釋放一次空間,而第二個要釋放兩次。但是第二種方法也是存在好處的,就是它的的訪問速度會比第一種要快。
總結
以上是生活随笔為你收集整理的C语言程序设计 | 动态内存管理:动态内存函数介绍,常见的动态内存错误,柔性数组的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言程序设计 | 结构体,枚举,联合
- 下一篇: C语言程序设计 | 程序环境和预处理:翻