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