C語言動態(tài)內存管理深入探討
1.動態(tài)內存開辟的原因
常見的內存開辟方式
int val = 20;//在??臻g上開辟四個字節(jié)的空間
char arr[10] = {0};//在??臻g上開辟10個字節(jié)的連續(xù)空間
上面開辟空間的方式有兩個特點:
1.空間開辟的大小是固定的;
2. 數組在聲明的時候,必須指定數組的長度,它所需要的空間在編譯時分配;
但是對于空間的需求,不只是上面的情況,有時候需要的空間大小在程序運行的時候才能得知,這時候數組的編譯時開辟空間的方式就不能滿足了。
所以需要動態(tài)開辟內存
2.動態(tài)內存函數的介紹
動態(tài)內存的開辟都是在內存的堆區(qū)中進行開辟的
2.1malloc和free
C語言提供了一個動態(tài)開辟內存的函數:
void* malloc(size_t size);
malloc函數向內存申請一塊連續(xù)可用的空間,并返回指向這塊空間起始位置的指針。
1.如果開辟成功,則返回一個指向開辟好的空間的指針;
2.如果開辟失敗,則返回一個NULL指針,因此malloc的返回值一定要做檢查,不然可能會造成野指針的問題;
3.返回值的類型時void*,所以malloc函數并不知道開辟空間的類型,具體在使用的時候由使用者自己來決定返回值的類型;
4.如果參數size為0,此時malloc函數的行為是標準未定義的,取決于程序運行時使用的編譯器;
malloc的使用:
vint main() { int* p = (int*)malloc(40);//向內存申請一塊40字節(jié)的空間,并對放回的指針類型轉換 if (p == NULL)//返回NULL指針時讓打印錯誤信息并讓程序結束 { perror("malloc"); return 1; } int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } return 0; }
要記得包含頭文件<stdlib.h>
這樣就對開辟的內存空間進行了一個使用了,但是還有點問題,因為向內存申請的空間沒有進行釋放。
所以這時就引入了另一個函數free
C語言提供了另一個函數free,專門用來做動態(tài)內存的釋放和回收的
void free(void* ptr);
free函數用來釋放動態(tài)開辟的內存
1.如果參數ptr指向的空間是不是動態(tài)開辟的,那么free函數的行為是未定義的;
2.如果參數ptr是NULL指針,則free函數什么事也不做;
malloc和free函數都聲明在stdlib.h頭文件中
free的使用:
int main() { int* p = (int*)malloc(40);//向內存申請一塊40字節(jié)的空間,并對放回的指針類型轉換 if (p == NULL)//返回NULL指針時讓打印錯誤信息并讓程序結束 { perror("malloc"); return 1; } int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } free(p);//釋放p所指向的動態(tài)內存 p == NULL;//將p的值置為NULL return 0; }
free的參數一定是動態(tài)開辟內存空間的那個起始位置的地址,否則會報錯
在用free釋放完動態(tài)開辟的內存之后,要對之前指向動態(tài)開辟空間的那個指針置為NULL,因為那塊動態(tài)開辟的空間已經被操作系統(tǒng)回收了,沒有了訪問的權限,所以要讓p的值為NULL,避免野指針的問題。
如果對動態(tài)內存開辟的空間沒有釋放掉,會出現(xiàn)一個內存泄漏的問題。
2.2calloc
C語言還提供了一個calloc函數,calloc也是用來進行動態(tài)內存的分配
void* calloc(size_t num, size_t size);
1.calloc的功能是為num個字節(jié)大小為size的元素開辟一個空間,并且把空間的每個字節(jié)的數據都初始化為0,然后返回這塊連續(xù)空間的起始位置的地址;
2.與malloc函數的區(qū)別只在于,calloc在返回地址之前會把申請的空間的每個字節(jié)的數據都初始化為全0;
calloc的使用:
int main() { int* p = (int*)calloc(10, 4); if (p == NULL) { perror("calloc"); return 1; } free(p); p = NULL; return 0; }
內存情況:
可以看到,動態(tài)開辟的40個字節(jié)的空間都被初始化為全0
所以如果要對動態(tài)開辟的空間進行初始化,可以直接使用calloc函數來完成
2.3realloc
有時會發(fā)現(xiàn)申請的空間太大或者太小了,為了合理的使用內存,一定會對內存的大小做一個靈活的調整,那么realloc函數就可以做到對動態(tài)開辟內存大小的調整
realloc函數的出現(xiàn)讓動態(tài)內存管理更加靈活
void* realloc (void* ptr, size_t size);
1.ptr是要調整的內存空間;
2.size是調整之后的新大??;
3.返回值為調整之后的內存起始位置;
4.realloc函數在調整原內存空間大小的基礎上,還會將原來內存中的數據移動到新的空間;
realloc函數在調整內存空間時存在兩種情況:
情況1:要調整的空間之后有足夠的空間來存放調整之后的大小
情況2:要調整的空間之后沒有足夠大的空間
如果是情況1,那么就在原有的內存之后追加新的空間,原來空間的數據不發(fā)生變化。
如果是情況2,原有空間之后沒有足夠多的空間,此時就會在堆空間上另找一個合適大小的連續(xù)空間來使用,這樣函數返回的是一個新的內存地址
并且realloc函數還會將原空間的數據移動到新的空間。
如果realloc函數在堆區(qū)中都找不到一塊合適的空間,則會返回NULL指針。
realloc的使用:
int main() { int* p = (int*)calloc(10, 4); if (p == NULL) { perror("calloc"); return 1; } p = realloc(p, 1000); if (p == NULL) { perror("realloc"); return 1; } free(p); p = NULL; return 0; }
其次,realloc函數還能使原有空間變?。?/p>
使用:
int main() { int* p = (int*)calloc(10, 4); if (p == NULL) { perror("calloc"); return 1; } p = realloc(p, 20); if (p == NULL) { perror("realloc"); return 1; } free(p); p = NULL; return 0; }
內存情況:
3.常見的動態(tài)內存錯誤
3.1對NULL指針的解引用操作
這里編譯器直接把對NULL指針的解引用操作給取消掉了,如果在其他的編譯器下運行,可能會出現(xiàn)問題,所以一定要對動態(tài)開辟內存函數的返回值進行一個NULL指針的判斷。
3.2對動態(tài)開辟空間的越界訪問
int main() { int* p = (int*)malloc(40); if (p == NULL) { perror("malloc"); return 1; } int i = 0; for (i = 0; i <= 10; i++) { *(p + i) = i; } free(p); p = NULL; return 0; }
其中*(p + 10) = 10;時對動態(tài)開辟的空間進行了一個越界訪問了,編譯器直接報錯
3.3對非動態(tài)開辟內存使用free
對棧區(qū)上的空間使用free:
int main() { int a = 0; free(&a); return 0; }
此時編譯器也會給出一個錯誤
3.4使用釋放一塊動態(tài)開辟內存的一部分
int main() { int* p = (int*)malloc(40); p++; free(p); return 0; }
此時p沒有指向動態(tài)開辟內存的起始位置
編譯器同樣給出了一個錯誤
3.5對同一塊動態(tài)內存多次釋放
int main() { int* p = (int*)malloc(40); free(p); free(p); return 0; }
p已經釋放過了
3.6動態(tài)開辟內存忘記釋放(內存泄漏)
在向內存申請了一塊空間之后沒有對其進行釋放會造成內存泄漏的問題
會迅速吃滿你的內存
int main() { while (1) { malloc(40); } return 0; }
如圖:
如果程序在沒有結束之前申請的內存都沒有進行釋放的話,就會出現(xiàn)內存泄漏的問題。所以在申請好一塊內存之后要記得對其進行釋放。
總結:
忘記釋放不再使用的動態(tài)內存開辟的空間就會造成內存泄漏的問題,而且動態(tài)開辟的空間要正確釋放。
4.練習
4.1練習1
void GetMemory(char* p) { p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }
請問運行Test 函數會有什么樣的結果?
先創(chuàng)建了一個字符指針變量賦值為NULL,然后調用函數GerMemory,調用函數時形參只是一份對實參的臨時拷貝,函數調用時,申請了一塊動態(tài)開辟內存,但是并沒有返回p,p在函數調用結束后銷毀了,所以此時str指向的還是NULL,strcpy使用時對NULL指針進行了解引用,造成了非法訪問,野指針的問題,也造成了動態(tài)內存錯誤
4.1練習2
char* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); }
請問運行Test 函數會有什么樣的結果?
調用GetMemory函數時在棧區(qū)開辟了一塊數組的空間,而在函數調用結束后數組開辟的空間已經被回收了,str接收了p的值,而p所指向的空間已經被回收了,所以p所指向的值也會發(fā)生變化,所以此時printf(str);打印的會是未知的結果
4.3練習3
void GetMemory(char** p, int num) { *p = (char*)malloc(num); } void Test(void) { char* str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); }
請問運行Test 函數會有什么樣的結果?
Getmemory函數時傳址調用,將申請的動態(tài)開辟內存空間的起始位置地址給了str,所以能夠正常訪問開辟的內存。不過沒有進行free會造成內存泄漏的問題。
4.4練習4
void Test(void) { char* str = (char*)malloc(100); strcpy(str, "hello"); free(str); if (str != NULL) { strcpy(str, "world"); printf(str); } }
請問運行Test 函數會有什么樣的結果?
此時已經str所指向的動態(tài)內存空間已經釋放掉了,會造成非法訪問。
5.C/C++程序的內存開辟
C/C++程序內存分配的幾個區(qū)域:
1.棧區(qū):在執(zhí)行函數時,函數內部變量的存儲單元都可以在棧上創(chuàng)建,函數結束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。棧區(qū)主要存放運行函數而存放的局部變量、函數參數、返回數據、返回地址等。
2.堆區(qū):一般有程序員分配,若程序員不釋放,程序結束時可能由OS回收,分配方式類似于鏈表。
3.數據段(靜態(tài)區(qū)):存放全局變量,靜態(tài)數據,程序結束后由系統(tǒng)釋放。
4.代碼段:存放函數體的二進制代碼
實際上普通的局部變量是在棧區(qū)分配空間的,棧區(qū)的特點是在上面創(chuàng)建的變量出了作用域就銷毀,但是被static修飾的變量存放在數據段,數據段的特點是,在上面創(chuàng)建的變量,直到程序結束才銷毀,所以static修飾的變量生命周期變長了。
到此這篇關于C語言動態(tài)內存管理深入探討的文章就介紹到這了,更多相關C語言動態(tài)內存管理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C語言動態(tài)與靜態(tài)分別實現(xiàn)通訊錄詳細過程
這篇文章主要為大家介紹了C語言動態(tài)與靜態(tài)分別實現(xiàn)通訊錄,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02