備案網(wǎng)站轉(zhuǎn)入阿里云管理方面的培訓(xùn)課程
? 所謂動態(tài)內(nèi)存管理,就是使得內(nèi)存可以動態(tài)開辟,想使用的時候就開辟空間,使用完之后可以銷毀,將內(nèi)存的使用權(quán)還給操作系統(tǒng),那么動態(tài)開辟內(nèi)存有什么用呢?
? 假設(shè)有這么一種情況,你在一家公司中工作,該公司開發(fā)了一款app,要有用戶來使用這款app,那么添加用戶信息的時候需要開辟內(nèi)存空間,但是該開辟的內(nèi)存空間又是不確定的,開辟小了不夠用,開辟多了又會浪費內(nèi)存空間,以后如果繼續(xù)有用戶使用該app,又需要開辟內(nèi)存空間,所以這個時候就需要動態(tài)開辟內(nèi)存空間,要用的時候就開辟,不用的時候就銷毀,這就是動態(tài)內(nèi)存管理存在的意義。
? 學(xué)習(xí)動態(tài)內(nèi)存管理主要是學(xué)習(xí)?4?個函數(shù),分別是malloc,free,calloc,realloc 這四個函數(shù),這幾個函數(shù)都被包含在 stdlib.h?頭文件里,使用的時候需要包含頭文件stdlib.h。接下來就來依次講解這四個函數(shù)。
? 值得注意的一點是,動態(tài)內(nèi)存管理對于以后學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)是必要知識,只有學(xué)好了動態(tài)內(nèi)存管理,才能學(xué)好數(shù)據(jù)結(jié)構(gòu)。
目錄
1? malloc函數(shù)
2? free函數(shù)
3? calloc函數(shù)
4? realloc函數(shù)
5? 動態(tài)內(nèi)存的注意事項
1) 一定要檢查動態(tài)開辟內(nèi)存是否成功
2) 防止對非動態(tài)開辟的內(nèi)存進行釋放
?3) 要避免使用free函數(shù)釋放一部分動態(tài)開辟內(nèi)存
4) 防止對同一塊內(nèi)存空間進行多次釋放?
5) 動態(tài)開辟內(nèi)存后,一定要記得使用free函數(shù)釋放
6? 柔性數(shù)組
1) 柔性數(shù)組的定義
2) 柔性數(shù)組的特點
?3) 柔性數(shù)組的使用
4) 柔性數(shù)組的優(yōu)勢
7? C/C++在內(nèi)存中的區(qū)域劃分
1? malloc函數(shù)
使用malloc函數(shù)時,需要注意以下幾點:
1 | malloc函數(shù)的參數(shù)為字節(jié),也就是想要開辟空間的字節(jié)數(shù) |
2 | 如果開辟失敗,那么malloc函數(shù)的返回值為空指針 |
3 | 如果開辟成功,那么malloc函數(shù)的返回值為viod*,需要強制類型轉(zhuǎn)換為想要開辟空間數(shù)據(jù)的指針類型,如:int*,float*等等 |
4 | 如果參數(shù)size為0,那么malloc的行為是未定義 |
使用malloc函數(shù)的例子如下:
#include<stdio.h>
#include<stdlib.h>int main()
{ //malloc返回值需要強轉(zhuǎn)為int*int num = 0;scanf("%d", &num);int* arr = (int*)malloc(sizeof(num) * 20);//參數(shù)為字節(jié)//使用malloc的時候一定要判斷返回值,如果開辟失敗,那么退出程序if (arr == NULL){//perror函數(shù)的功能為打印錯誤信息perror("malloc fail!\n");//exit為退出函數(shù)exit(1);}//開辟成功for (int i = 0; i < num; i++){scanf("%d", &arr[i]);}for (int i = 0; i < num; i++){printf("%d ", arr[i]);}return 0;
}
運行結(jié)果:
? 在上述代碼中, 共開辟了10個空間,依次向arr數(shù)組里面輸入了1,2,3,4,5,6,7,8,9,10這10個數(shù)據(jù)。
? 在代碼中最關(guān)鍵的一段代碼就是判斷malloc返回值與強轉(zhuǎn)的那兩段代碼,在動態(tài)開辟數(shù)據(jù)時,一定要判斷?malloc函數(shù)返回值,也就是判斷有沒有成功動態(tài)開辟內(nèi)存空間。
2? free函數(shù)
? free函數(shù)的功能主要是用來釋放動態(tài)開辟的空間,也就是將內(nèi)存空間的使用權(quán)歸還給操作系統(tǒng),其注意事項有以下兩條:
1 | free函數(shù)的參數(shù)為指針類型,這個指針所指向的空間必須是動態(tài)開辟的,否則其行為是未定義的 |
2 | 如果參數(shù)ptr為NULL,那么free函數(shù)什么都不做 |
? free函數(shù)一般是配合其他動態(tài)開辟內(nèi)存的函數(shù)使用,如malloc函數(shù),還有之后的calloc,realloc函數(shù),值得注意的一點是,動態(tài)開辟的內(nèi)存一定要使用free函數(shù)釋放掉內(nèi)存,否則可能會出現(xiàn)內(nèi)存泄露的情況,正確使用free函數(shù)的例子如下:
#include<stdio.h>
#include<stdlib.h>int main()
{ //malloc返回值需要強轉(zhuǎn)為int*int num = 0;scanf("%d", &num);int* arr = (int*)malloc(sizeof(num) * 20);//參數(shù)為字節(jié)//使用malloc的時候一定要判斷返回值,如果開辟失敗,那么退出程序if (arr == NULL){//perror函數(shù)的功能為打印錯誤信息perror("malloc fail!\n");//exit為退出函數(shù)exit(1);}//開辟成功for (int i = 0; i < num; i++){scanf("%d", &arr[i]);}for (int i = 0; i < num; i++){printf("%d ", arr[i]);}//使用完之后用free函數(shù)銷毀動態(tài)開辟的空間free(arr);//釋放完之后記得要把指針置為NULL,否則arr會變成野指針arr = NULL;return 0;
}
? 在上述代碼中,使用完free函數(shù)釋放了arr指針所指向的動態(tài)開辟的空間,也就是把動態(tài)開辟的空間還給了操作系統(tǒng),但是arr指針本身還是指向原來動態(tài)開辟的空間,所以釋放完之后,要把arr置為NULL,否則arr就變成了野指針,會越界訪問。
3? calloc函數(shù)
? calloc函數(shù)不同于以上兩個函數(shù),calloc函數(shù)有兩個參數(shù),第一個參數(shù)是想要動態(tài)開辟空間的元素個數(shù),另一個參數(shù)是想要動態(tài)開辟的每個元素的字節(jié)大小,如開辟10個整型空間,就可以這樣寫:
calloc(10, sizeof(int))
相同點 | 區(qū)別 | |
---|---|---|
1 | 返回值都為void*,都需要對返回值進行強制類型轉(zhuǎn)換 | malloc函數(shù)只有一個參數(shù),為開辟的空間字節(jié)的大小,calloc函數(shù)有兩個參數(shù),第一個參數(shù)是開辟空間元素的個數(shù),第二個參數(shù)是每個元素的字節(jié)數(shù) |
2 | 都是開辟成功會返回動態(tài)開辟空間首元素地址,開辟失敗返回NULL | calloc函數(shù)會將所有元素初始化為0,malloc不會,只是開辟空間,不進行初始化 |
使用calloc函數(shù)的例子如下:
#include<stdio.h>
#include<stdlib.h>int main()
{int* arr = (int*)calloc(10, sizeof(int));//開辟失敗if (arr == NULL){perror("calloc fail!\n");exit(1);}//開辟成功for (int i = 0; i < 10; i++){printf("%d ", arr[i]);}//使用完之后要釋放free(arr);//釋放完之后置為NULLarr = NULL;return 0;
}
運行結(jié)果:
4? realloc函數(shù)
? realloc函數(shù)是這四個函數(shù)里面最復(fù)雜的一個函數(shù),其最復(fù)雜就是因為其能夠?qū)崿F(xiàn)增容。
? 不管是前面的malloc函數(shù),還是calloc函數(shù)都只能實現(xiàn)動態(tài)開辟一塊空間,并不能根據(jù)已有空間來實現(xiàn)增容的效果,而realloc函數(shù)可以在原有空間的基礎(chǔ)上實現(xiàn)對原有空間的擴大,所以有了realloc函數(shù)就可以對內(nèi)存空間做靈活的調(diào)整了。
? ?使用realloc函數(shù)的注意事項如下:
1 | 第一個參數(shù)為指向要擴容的空間的指針 |
2 | 第二個參數(shù)為增容之后空間的大小,注意是增容之后空間的總大小,而不是增容的空間大小,如原來空間為10,想要增容10個空間,第二個參數(shù)為20 |
3 | 如果開辟成功,返回值為指向增容后所有空間的指針;如果開辟失敗,返回值為NULL |
? 對于realloc函數(shù)增容,有兩種情況:
?1) 如果原有空間之后有足夠的空間來進行增容,那么就會在原有空間之后追加空間,原有空間數(shù)據(jù)不變,返回值與第一個參數(shù)相同。
? 2) 如果原有空間后面的空間不夠要增容的空間大小,那么就會在內(nèi)存的堆區(qū)上另找一塊內(nèi)存空間大小(原有空間 + 要增容的空間)足夠的連續(xù)空間,并把原有空間的數(shù)據(jù)復(fù)制到新開辟的空間上,然后釋放舊空間,返回新開辟空間的首元素地址。
? 使用realloc函數(shù)的例子如下:
#include<stdio.h>
#include<stdlib.h>int main()
{int n = 0;scanf("%d", &n);int* arr = (int*)malloc(sizeof(int) * n);//先用一個中間變量來接收增容后的地址int* tmp = (int*)realloc(arr, sizeof(int) * 2 * n);//開辟失敗if (tmp == NULL){perror("realloc fail!\n");exit(1);}//開辟成功,將增容后空間地址賦給原有空間地址arr = tmp;n = 2 * n;for (int i = 0; i < n; i++){scanf("%d", arr + i);}for (int i = 0; i < n; i++){printf("%d ", arr[i]);}//使用完之后要銷毀free(arr);//銷毀之后置為NULLarr = NULL;return 0;
}
運行結(jié)果:
? 需要注意的一點是,在使用realloc函數(shù)增容的時候,一定要先用一個中間變量接受增容后的地址,一定要在確保開辟成功之后,再把增容后的地址賦給舊空間的地址,要是直接賦給舊空間的地址,一旦開辟失敗,那么舊空間就找不到了。
5? 動態(tài)內(nèi)存的注意事項
1) 一定要檢查動態(tài)開辟內(nèi)存是否成功
? 如果不檢查動態(tài)開辟空間函數(shù)的返回值,如果開辟失敗就會造成NULL指針的解引用,如:
#include<stdio.h>
#include<stdlib.h>int main()
{int* arr = (int*)malloc(sizeof(int));*arr = 10;free(arr);arr = NULL;return 0;
}
? 在上述代碼里面,開辟成功了還好,一旦開辟失敗就會造成對NULL指針的解引用,勢必會報錯,正確的代碼應(yīng)該這樣寫:
#include<stdio.h>
#include<stdlib.h>int main()
{int* arr = (int*)malloc(sizeof(int));//檢查返回值是否為NULLif (arr == NULL){perror("malloc fail!\n");exit(1);}//開辟成功*arr = 10;free(arr);arr = NULL;return 0;
}
2) 防止對非動態(tài)開辟的內(nèi)存進行釋放
? 對非動態(tài)開辟的內(nèi)存進行free函數(shù)釋放,其行為是未知的,如以下這個代碼:
#include<stdlib.h>int main()
{int arr[] = {1, 2, 4};free(arr);return 0;
}
在運行的時候會出現(xiàn)下面這種情況:
?3) 要避免使用free函數(shù)釋放一部分動態(tài)開辟內(nèi)存
? 在使用free函數(shù)釋放動態(tài)開辟空間時,很容易讓指向動態(tài)開辟空間的指針改變指向位置,如:
#include<stdlib.h>
#include<stdio.h>int main()
{int* ptr = (int*)malloc(sizeof(int) * 10);if (ptr == NULL){exit(1);}for (int i = 0;i < 10; i++){scanf("%d", ptr + i);}printf("%p ", ++ptr);free(ptr);ptr = NULL;
}
? 在上述代碼里面,在釋放空間的時候,ptr指針已經(jīng)不再指向原來動態(tài)開辟空間的首元素的地址,而是指向的是第二個元素的地址,在運行代碼時,vs編譯器也會發(fā)生錯誤:
? 所以在動態(tài)開辟內(nèi)存后,我們要避免改變動態(tài)開辟內(nèi)存指針的指向。
4) 防止對同一塊內(nèi)存空間進行多次釋放?
#include<stdlib.h>int main()
{int* ptr = (int*)malloc(40);if (ptr == NULL){exit(2);}free(ptr);free(ptr);return 0;
}
?運行后,同樣會發(fā)生錯誤:
? 所以在使用free函數(shù)釋放完空間之后,一定要記得把指針置為NULL,防止對其多次釋放。
5) 動態(tài)開辟內(nèi)存后,一定要記得使用free函數(shù)釋放
? 動態(tài)開辟的內(nèi)存會在以下兩種情況下歸還給操作系統(tǒng):
(1) 程序運行結(jié)束時
(2) 使用free函數(shù)釋放時
? 所以一旦一個程序不停止運行,而又沒有free函數(shù)主動釋放內(nèi)存,就會造成動態(tài)開辟的空間一直占用,內(nèi)存空間越來越少,就會造成內(nèi)存空間的浪費,也就是內(nèi)存泄露。
6? 柔性數(shù)組
1) 柔性數(shù)組的定義
? 在一個結(jié)構(gòu)體里面,最后一個元素允許是未知大小的數(shù)組,這個數(shù)組就叫做柔性數(shù)組成員。注意,柔性數(shù)組一定是在結(jié)構(gòu)體里面創(chuàng)建的。
? 上述定義可能比較抽象,下面舉個例子:
struct A
{int i;int a[0];
};
? 上述代碼里面的a數(shù)組就是柔性數(shù)組成員,數(shù)組元素個數(shù)為0,代表沒有成員。如果上述代碼在編譯器報錯的話,也可以寫成以下代碼:
struct A
{int i;int a[];
};
? 上述代碼的a數(shù)組也是柔性數(shù)組,數(shù)組里面的元素個數(shù)不寫,也代表數(shù)組里面沒有元素。
2) 柔性數(shù)組的特點
? 柔性數(shù)組的特點如下:
1 | 結(jié)構(gòu)體中的柔性數(shù)組成員前必須有一個成員 |
2 | 用 sizeof 關(guān)鍵字返回結(jié)構(gòu)體的大小不包括柔性數(shù)組的內(nèi)存 |
3 | 包含柔性數(shù)組成員的結(jié)構(gòu)體用?malloc?函數(shù)進行內(nèi)存的動態(tài)分配,且分配的內(nèi)存大小應(yīng)該大于結(jié)構(gòu)體的大小,以適應(yīng)柔性數(shù)組的預(yù)期大小?? |
如:
#include<stdio.h>typedef struct A
{int i;int a[0];
}A;int main()
{int size = sizeof(A);printf("%d ", size);return 0;
}
運行結(jié)果為:
通過上述代碼,可以看到,含有柔性數(shù)組成員的結(jié)構(gòu)體A,其大小確實為4個字節(jié),不包含柔性數(shù)組成員a數(shù)組的大小。?
?3) 柔性數(shù)組的使用
? 對于含有一個柔性數(shù)組成員的結(jié)構(gòu)體,應(yīng)該使用malloc函數(shù)來動態(tài)開辟空間,例如:
#include<stdio.h>
#include<stdlib.h>typedef struct A
{int x;int a[0];
}A;int main()
{A* pa = (A*)malloc(sizeof(A) + 10 * sizeof(int));//判斷是否開辟成功if (pa == NULL){perror("malloc fail!\n");exit(1);}pa->x = 10;for (int i = 9;i >= 0; i--){pa->a[i] = i;}printf("%d ", pa->x);for (int i = 0;i < 10;i++){printf("%d ", pa->a[i]);}//使用完不要忘記銷毀free(pa);//釋放后要置為NULLpa = NULL;return 0;
}
運行結(jié)果為:
上述代碼在使用malloc函數(shù)開辟帶有柔性數(shù)組結(jié)構(gòu)體成員的內(nèi)存空間時,malloc函數(shù)里面的參數(shù)應(yīng)寫的是 sizeof(A) + sizeof(int) * 10 ,而不是直接寫字節(jié)個數(shù),這樣寫不僅不用計算結(jié)構(gòu)體的大小(結(jié)構(gòu)體存在內(nèi)存對齊現(xiàn)象,計算起來比較麻煩),而且比較直觀,后面的 sizeof(int) * 10 就是為柔性數(shù)組成員開辟的空間。
4) 柔性數(shù)組的優(yōu)勢
??其實上述柔性數(shù)組的功能也可以通過在結(jié)構(gòu)體里添加一個指針變量來達到,如:
#include<stdio.h>
#include<stdlib.h>typedef struct A
{int x;int* pi;
}A;int main()
{A* pa = (A*)malloc(sizeof(A));if (pa == NULL){perror("malloc1 fail!\n");exit(1);}pa->x = 10;pa->pi = (int*)malloc(sizeof(int) * 10);if (pa->pi == NULL){perror("malloc2 fail!\n");exit(2);}for (int i = 9;i >= 0;i--){pa->pi[i] = i;}printf("%d ", pa->x);for (int i = 0;i < 10;i++){printf("%d ", pa->pi[i]);}//一定要先釋放結(jié)構(gòu)體里開辟的數(shù)組空間free(pa->pi);//再釋放開辟的結(jié)構(gòu)體空間free(pa);pa = NULL;return 0;
}
運行結(jié)果為:
可以看到在結(jié)構(gòu)體里添加一個指針變量同樣可以達到類似于柔性數(shù)組的功能,那么柔性數(shù)組相比于用指針來實現(xiàn)有什么優(yōu)勢呢?
1 | 柔性數(shù)組容易進行內(nèi)存釋放:通過上述兩種實現(xiàn)方式,我們可以看到,通過柔性數(shù)組實現(xiàn)只需要進行一次free釋放,而使用指針實現(xiàn)需要進行兩次free釋放,所以用柔性數(shù)組實現(xiàn)更容易進行內(nèi)存釋放,不容易出現(xiàn)內(nèi)存泄露情況 |
2 | 柔性數(shù)組有利于提高訪問速度:在使用柔性數(shù)組實現(xiàn)時,只進行了一次malloc動態(tài)開辟空間,所以開辟的是一塊連續(xù)的內(nèi)存空間;而在使用指針實現(xiàn)時,進行了兩次malloc動態(tài)開辟空間,使其內(nèi)存不一定是連續(xù)的。所以使用柔性數(shù)組可以提高訪問速度,而且會有利于減少內(nèi)存碎片。 |
?
? ?
7? C/C++在內(nèi)存中的區(qū)域劃分
? C語言或者C++語言共將內(nèi)存空間劃分為以下幾個區(qū)域:
區(qū)域 | 存放內(nèi)容 |
---|---|
棧區(qū)(Stack) | 主要是用來進行函數(shù)棧幀的創(chuàng)建,還用來存放一些局部變量、函數(shù)參數(shù)、返回數(shù)據(jù)與返回地址等,在堆上開辟的空間是在函數(shù)運行完后被銷毀。 |
堆區(qū)(Heap) | 向malloc、calloc、realloc函數(shù)動態(tài)開辟的空間一般都在堆區(qū)上開辟,在堆區(qū)上開辟的空間要么是由程序員主動釋放,要么是在程序運行結(jié)束時,由操作系統(tǒng)自動收回。 |
數(shù)據(jù)段(靜態(tài)區(qū)) | 主要用來存放全局變量和由static關(guān)鍵字修飾的靜態(tài)變量,在靜態(tài)區(qū)開辟的空間是在程序運行結(jié)束時由系統(tǒng)自動釋放。 |
代碼段 | 用來存放函數(shù)體的二進制代碼。 |