C語言動(dòng)態(tài)內(nèi)存的分配最全面分析
為什么有動(dòng)態(tài)內(nèi)存分布
大家發(fā)現(xiàn)一個(gè)問題沒有,就是我們之前寫代碼創(chuàng)建數(shù)組的時(shí)候,似乎都存在著這么一個(gè)問題,就是我們開辟一個(gè)數(shù)組的時(shí)候,這個(gè)數(shù)組的大小好像都是固定的,不能改變的,比如說我這里創(chuàng)建了一個(gè)能裝100個(gè)字符的數(shù)組,那么這個(gè)數(shù)組在這整個(gè)程序中的大小都是不會(huì)改變的,那么我們這里的程序在以后的使用中導(dǎo)致這個(gè)數(shù)組裝不下了,那么是不是就會(huì)出現(xiàn)問題啊,那么我們又不知道這個(gè)數(shù)組到底得多大,到底多少才能合適,小了裝不下,多了又浪費(fèi)空間,所以為了解決這個(gè)情況,我們這里就給出了動(dòng)態(tài)內(nèi)存分布這個(gè)概念,那么這篇文章就帶著大家學(xué)習(xí)一下如何來實(shí)現(xiàn)我們的動(dòng)態(tài)內(nèi)存分布。
malloc函數(shù)的使用
那么我們這里的動(dòng)態(tài)內(nèi)存分布是通過我們的函數(shù)來實(shí)現(xiàn)的,那么我們這里首先來看看malloc函數(shù)的介紹
那么我們看到這個(gè)函數(shù)的作用就是開辟一個(gè)空間,使用這個(gè)函數(shù)需要引用的頭文件stdlib.h,然后這個(gè)函數(shù)需要的一個(gè)參數(shù)就是一個(gè)整型,那么這個(gè)函數(shù)所開辟的空間的大小就是根據(jù)這個(gè)整型來確定的,然后這個(gè)函數(shù)的返回值是void*類型的一個(gè)指針,那么這里就有小伙伴們會(huì)感到疑惑了,為什么這里返回的類型是一個(gè)void*類型的指針呢?那么這里我們就要想象一個(gè)場景了,在編寫這個(gè)函數(shù)的時(shí)候,我們的作者知不知道你拿這個(gè)開辟的空間去干嘛,他不知道,他不知道你是拿去存儲(chǔ)一個(gè)char類型的變量還是一個(gè)int類型的變量還是一個(gè)結(jié)構(gòu)體類型的變量,所以我們這里就只能返回一個(gè)void*類型的指針來作為返回值,所以我們在使用這個(gè)函數(shù)的時(shí)候就得強(qiáng)制類型轉(zhuǎn)化一下,轉(zhuǎn)換成你想要使用的類型,那么看到這里想必大家硬干能夠了解這個(gè)函數(shù)使用的方法,那么我們這里就來看看這個(gè)函數(shù)使用例子,那么我們這里假設(shè)要開辟40個(gè)字節(jié)的空間,然后用這個(gè)開辟好的空間來存儲(chǔ)10個(gè)整型的變量,然后將這個(gè)10個(gè)整型變量初始化為1,2,3,4,5,6,7,8,9,10,那么我們這里的代碼如下:
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(40); int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } for (i = 0; i < 10; i++) { printf("%d ", *(p+i)); } return 0; }
那么這里我們需要40個(gè)字節(jié)的大小的內(nèi)存,所以我們這里就在括號(hào)里面寫進(jìn)一個(gè)40,然后我們這里需要的整型的指針,所以我們這里就在接收時(shí)將他強(qiáng)制類型轉(zhuǎn)換一下,那么我們這里的指針p指向的就是這個(gè)開辟的內(nèi)存的其實(shí)位置,那么我們這里的p他是int*類型,那么我們這里每加一個(gè)1就會(huì)跳過4個(gè)字節(jié),解引用的時(shí)候訪問4個(gè)字節(jié),那么我們這里在使用的時(shí)候就可以挨個(gè)跳過挨個(gè)解引用挨個(gè)賦值,用for循環(huán)來實(shí)現(xiàn)上訴的功能,我們在打印的時(shí)候便也是跟上面的思路差不多,那么我們這里就可以來看看這個(gè)代碼運(yùn)行的結(jié)果為:
那么我們這里使用這個(gè)函數(shù)的時(shí)候還需要知道的一點(diǎn)就是我們這里的開辟空間的時(shí)候,如果開辟成功則這個(gè)函數(shù)返回的是一個(gè)指向開辟好空間的指針。如果開辟失敗,則返回一個(gè)NULL指針,那么我們上面的代碼應(yīng)為他需要的空間較小,所以他開辟成功了,但是如果我們這里開辟的空間較大的話,我們這里是會(huì)返回一個(gè)NULL指針的,那么我們這里如果不做檢查的話,我們就會(huì)對空指針做一些操作解引用啥的等等,那么這樣的話就會(huì)報(bào)出錯(cuò)誤,因?yàn)槲覀冞@里規(guī)定的就是不準(zhǔn)對NULL解引用使用,所以我們每次在使用這個(gè)函數(shù)的時(shí)候最好加上一個(gè)判斷條件,判 斷它是否開辟成功了,那么我們這里就可以將我們的代碼進(jìn)行一下改進(jìn):
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> int main() { int* p = (int*)malloc(100000000000000000); int i = 0; if (p == NULL) { printf("%s", strerror(errno)); return 1; } for (i = 0; i < 10; i++) { *(p + i) = i; } for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); } return 0; }
那么我們在這里就進(jìn)行了一下簡單的修改,我們這里加了一個(gè)if語句用來判斷我們這里的內(nèi)存是否開辟成功,那么我們這里就使用的是if語句,那么我們這里進(jìn)入這個(gè)if語句的內(nèi)部的條件就是當(dāng)我們這個(gè)的p等于空指針的時(shí)候,那么我們這里if語句的內(nèi)部就可以用到我們之前講的strerror錯(cuò)誤信息報(bào)告函數(shù),和errno來顯示我們這里的錯(cuò)誤碼,來顯示我們這里的錯(cuò)誤,然后再添加一個(gè)return 1 ,來結(jié)束我們這個(gè)程序,那么這樣的話我們既避免了解引用空指針,又可以顯示我們的錯(cuò)誤所在,那么我們講上面的代碼運(yùn)行一下就可以看到我們這里出錯(cuò)的原因:
那么我們這里就打印出來了我們這里出錯(cuò)誤的原因,那么我們這里就相當(dāng)于給我們這里的代碼上了一層保險(xiǎn)避免出錯(cuò),那么我們這里開辟的空間和我們之前之間創(chuàng)建一個(gè)變量和一個(gè)數(shù)組開辟的空間有什么區(qū)別?那么這里的區(qū)別肯定是有的,我們這里的動(dòng)態(tài)開辟的空間他開辟的位置是在我們的內(nèi)存中的堆區(qū),而我們的創(chuàng)建的變量的所申請的空間是在我們內(nèi)存中的棧區(qū),而我們棧區(qū)的內(nèi)存使用習(xí)慣是從高地址往低地址進(jìn)行使用,而我們的堆區(qū)中開辟的內(nèi)存就沒有這個(gè)習(xí)慣,而且我們在棧區(qū)中創(chuàng)建的變量的聲明周期往往都是所在的代碼塊,比如說我們在函數(shù)中創(chuàng)建的一個(gè)局部變量,那么這個(gè)變量的生命周期往往就是這個(gè)函數(shù)的內(nèi)部,出了這個(gè)函數(shù)它就沒了,但是我們在這個(gè)堆區(qū)申請的空間那就不大一樣,只要你不主動(dòng)的釋放那么他除了你程序的結(jié)束,他能一直都在所以這里就是我們另外的一個(gè)小小的區(qū)別,那么這里說到釋放又是一個(gè)怎么回事呢?那么這個(gè)問題我們待會(huì)再聊,那么這里有這么一個(gè)問題,就是大家可能聽過變長數(shù)組這個(gè)東西,那么有人就要問了,這個(gè)變長數(shù)組是不是也是動(dòng)態(tài)內(nèi)存中的一部分啊,因?yàn)樗亲冮L啊他的大小聽上去好像可以變啊,那么這里大家可能理解上就有那么一點(diǎn)點(diǎn)的偏差了,我們這里的變長數(shù)組并不是說他的長度可以變,而是說我們在創(chuàng)建這個(gè)數(shù)組的時(shí)候可以先輸入一個(gè)變量上去,這個(gè)變量可以在我們程序運(yùn)行起來的時(shí)候再·輸入這個(gè)變量的大小,那么這里就是我們的變長數(shù)組,他在內(nèi)存中申請的空間位置是在棧區(qū),而不是堆區(qū),所以我們這里的變長數(shù)組并不是我們的動(dòng)態(tài)內(nèi)存開辟的內(nèi)容,那么我們這里的變長數(shù)組的使用方式就是如下的代碼,那么這里還有一定那就是支持c99編譯器的才能使用我們這里的變長數(shù)組,那么我們這里的vs編譯器不支持c99標(biāo)準(zhǔn)所以用不了,那么我們這里的代碼形式如下:
#include<stdio.h> int main() { int n = 0; scanf("%d", &n); int arr[n] = { 0 }; return 0; }
free函數(shù)的用法
那么我們這一章的內(nèi)容叫做動(dòng)態(tài)的分配,那么這個(gè)動(dòng)態(tài)目前為止好像體現(xiàn)的不是很明顯啊,我們上面的代碼只體現(xiàn)出來了我們開辟了一個(gè)空間,但是這個(gè)功能我們普通的創(chuàng)建變量也有這個(gè)功能啊,那么我們就可以來講講我們這里的動(dòng)態(tài)內(nèi)存可以體現(xiàn)出來在哪里,我們上面的代碼
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> int main() { int* p = (int*)malloc(40); int i = 0; if (p == NULL) { printf("%s", strerror(errno)); return 1; } for (i = 0; i < 10; i++) { *(p + i) = i; } for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); } return 0; }
這里是申請了一個(gè)40個(gè)字節(jié)大小的空間,那么我們這里申請這個(gè)空間的目的就是打印1到10,那么我們這里打印完了之后是不是就用不到這個(gè)空間了,既然我們不用了那么我們這里是不是就可以講這個(gè)空間進(jìn)行釋放了呢?把這個(gè)空間還給我們的操作系統(tǒng)他好再分給其他的地方進(jìn)行使用,那么這樣的話我們的內(nèi)存利用效率是不是就會(huì)大大的提高了呢?對吧這就是我們動(dòng)態(tài)內(nèi)存的一個(gè)很好的特點(diǎn),相比于我們之前用的那個(gè)創(chuàng)建變量的方法這個(gè)是不是就靈活了很多啊,因?yàn)槲覀兡莻€(gè)方法你申請之后他就一直都在你無法進(jìn)行釋放嗎,那么這樣的話我們的利用率就會(huì)很低,所以這里釋放空間的方法就是我們這里的free函數(shù),那么我們這里就可以先看看我們free函數(shù)的基本的介紹:
那么我們這個(gè)free函數(shù)需要的參數(shù)就只有一個(gè)就是一個(gè)指針,而且沒有返回值,那么我們這里就可以講上面的代碼進(jìn)行修改加一個(gè)free函數(shù)上去來釋放我們這里開辟的一個(gè)空間
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> int main() { int* p = (int*)malloc(40); int i = 0; if (p == NULL) { printf("%s", strerror(errno)); return 1; } for (i = 0; i < 10; i++) { *(p + i) = i; } for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); } free(p); p = NULL; return 0; }
那么我們這里就可以使用調(diào)試來進(jìn)行一下觀察我們這里變化的過程
在釋放前我們這里內(nèi)存的數(shù)據(jù)就是0到9,那么我們來看看執(zhí)行一下我們這里的free函數(shù)看看會(huì)變成什么:
我們發(fā)現(xiàn)在執(zhí)行完我們這里free函數(shù)之后我們這里的內(nèi)存中的值都變成了隨機(jī)值,也就是說我們這里的內(nèi)存是還給了我們的操作系統(tǒng),但是我們這里發(fā)現(xiàn)了一個(gè)問題就是我們這里p的值是沒有發(fā)生變化的也就是說我們這里的p還是保留的原來的值,那么這里既然保留原來的值的話,我們是不是就可以通過這個(gè)只值來找到我們原來的那塊內(nèi)存啊,那么既然這里能夠找到這個(gè)塊空間的話我們是不是就可以訪問這里的內(nèi)存了啊,但是這塊內(nèi)存他已經(jīng)不屬于你的這個(gè)程序啊,但是你卻能夠找到這塊空間的話是不是就會(huì)報(bào)錯(cuò)誤了啊,因?yàn)檫@個(gè)時(shí)候我們的p已經(jīng)成為了一個(gè)野指針,所以我們這里在釋放空間之后還得干的一個(gè)步驟就是講我們這里的指針變量p的值賦值為一個(gè)空指針,這就是為了違法訪問而帶來的問題。那么看到的這里想必大家知道了我們的free函數(shù)使用的規(guī)則已經(jīng)作用但是這里還有幾個(gè)小點(diǎn)需要大家注意一下:
第一點(diǎn):
我們的free函數(shù)不能釋放不是動(dòng)態(tài)內(nèi)存開辟的空間,比如說我們下面的代碼:
#include<stdio.h> #include<stdlib.h> int main() { int a = 10; int* p = &a; free(p); return 0; }
那么我們這里就在棧區(qū)上面創(chuàng)建了一個(gè)變量,然后我們?nèi)〕鏊牡刂罚x值給了我們的指針變量p,然后我們在使用我們的free函數(shù),釋放我們這里的空間,那么這里我們在運(yùn)行之后就可以看到我們這里報(bào)出了錯(cuò)誤:
那么我們這里創(chuàng)建的變量是在內(nèi)存中的棧區(qū)開辟的空間,并不是在動(dòng)態(tài)內(nèi)存中開辟的空間,所以你是不能對其進(jìn)行釋放的,那么我們這里如果要進(jìn)行釋放的話就會(huì)報(bào)出錯(cuò)誤。
第二點(diǎn):
我們這里的free函數(shù)只能整體整體的進(jìn)行釋放,不能一部分一部分的來釋放,比如說我們一開始申請的是40個(gè)字節(jié),那么你釋放的時(shí)候就得將一次性將這40個(gè)字節(jié)的內(nèi)容全部都釋放掉,不能說這前面的20個(gè)字節(jié)還有用,后面的20個(gè)字節(jié)沒有用了,所以我們就找到后20個(gè)字節(jié)的起始位置然后把他釋放掉,那么這里是不可以的哈我們可以看看下面的代碼:
#include<stdio.h> #include<stdlib.h> int main() { int i = 0; int* p = (int*)malloc(40); for (i = 0; i < 10; i++) { *p = i; p++; if (i == 5) { free(p); } } return 0; }
那么我們這里一下子開辟了40個(gè)字節(jié)大小的空間,然后我們就將這個(gè)這個(gè)的起始地址轉(zhuǎn)換成int*類型的指針賦值給我們的p,然后我們就進(jìn)入了一個(gè)循環(huán),我們這個(gè)循環(huán)就對這個(gè)開辟的空間里面進(jìn)行賦值,每次循環(huán)我們就使這個(gè)p++,然后我們這里再在里面加上一個(gè)判斷的條件如果我們這里的i等于5的話我們就釋放這里p所指向的一個(gè)空間,然后return 1使得這個(gè)程序結(jié)束,那么我們這里將這個(gè)程序運(yùn)行起來之后就可以看到:
我們這里報(bào)錯(cuò)了,那么我們這里報(bào)錯(cuò)的原因就是因?yàn)槲覀冞@里的p在循環(huán)的過程中就已經(jīng)發(fā)生了改變,那么我們這里再進(jìn)行釋放的話指向的就不是一開始的地址,而是我們申請的內(nèi)存中的地址,那么這樣釋放的話就會(huì)出現(xiàn)問題因?yàn)檫@樣操作的話釋放的就是一部分的空間并不是整個(gè)的空間所以我們的編譯器就會(huì)報(bào)錯(cuò) 。
第三點(diǎn):
我們的free函數(shù)里面的參數(shù)要是是空指針的話,那么我們這里的free函數(shù)不會(huì)有任何的作用,就是啥也不干。
第四點(diǎn):
那么根據(jù)上面的第二點(diǎn)我們可以發(fā)現(xiàn)一件事情就是我們這里將p的值改變了,那么這就導(dǎo)致了我們無法找到一開始開辟空間的那個(gè)地址,那么這樣的話我們就無法釋放這個(gè)開辟好的內(nèi)存,我們就把這種情況稱為內(nèi)存泄漏,那么這就是一個(gè)非常嚴(yán)重的問題因?yàn)檫@個(gè)空間你不用你也不還給操作系統(tǒng),那么我們想用也用不著,那么久而久之我們的內(nèi)存就會(huì)越來越少直到無法運(yùn)行,那么這里我們就要提醒大家的就是使用動(dòng)態(tài)內(nèi)存的時(shí)候一定得記得如果不用的話得將內(nèi)存進(jìn)行釋放,不然就容易導(dǎo)致內(nèi)存泄漏的問題。當(dāng)然我們將程序結(jié)束了也可以釋放內(nèi)存。
calloc的用法
那么除了我們的malloc函數(shù)可以申請空間之外,我們函數(shù)庫里面還有一個(gè)函數(shù)也可以申請空間就是我們這里的calloc,那么我們來看看這個(gè)函數(shù)的基本參數(shù)和返回值,和介紹:
那么我們這個(gè)函數(shù)的作用跟我們的malloc函數(shù)的作用幾乎是一模一樣的,但是我們這里的參數(shù)是有區(qū)別的,我們的malloc是直接填入你想申請的字節(jié)數(shù),而我們這里是將參數(shù)分為了兩個(gè),第一個(gè)是你想開辟的空間里面有幾個(gè)元素,第二個(gè)是每個(gè)元素的大小是多少,那么我們這里可以通過下面的代碼來學(xué)習(xí)一下這個(gè)該如何來使用:
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> int main() { int* p = (int*)calloc(8, sizeof(int)); int i = 0; if (p == NULL) { printf("%s", strerror(errno)); return 1; } for (i = 0; i < 8; i++) { *(p + i) = i; } for (i = 0; i < 8; i++) { printf("%d ", *(p + i)); } free(p); p = NULL; return 0; }
那么我們這里在開辟內(nèi)存的時(shí)候就用了另外的一個(gè)函數(shù)calloc我們要是想開辟的空間能夠容納10個(gè)整型的話,那么我們這里就只用在第一個(gè)參數(shù)的位置填入8,表示開辟8個(gè)元素,然后第二個(gè)元素填入每個(gè)元素的大小即可,那么我們這里將其運(yùn)行一下:
就可以發(fā)現(xiàn)確實(shí)可以開辟成功,但是我們這里calloc函數(shù)跟我們的malloc函數(shù)還是有一個(gè)區(qū)別就是我們這里的calloc函數(shù)可以自動(dòng)的進(jìn)行初始化,將所用的元素都初始化為0,而我們的malloc卻不會(huì),那么我們這里可以通過調(diào)試來看看
我們可以看到我們這里確實(shí)是將所有的元素都初始化為了0,而我們的malloc卻是這樣的:
那么這里也是一個(gè)區(qū)別。
realloc的使用方法
盡管有了我們上面的三個(gè)函數(shù),但是我感覺這里依然有點(diǎn)不是那么的動(dòng)態(tài),感覺還差點(diǎn)什么?對要是我們能夠臨時(shí)的更改我們的內(nèi)存的大小的話,是不是就顯得更加的靈活了啊,比如說一開始我們申請了20個(gè)字節(jié)的空間,但是這20個(gè)空間要是不夠用了,我能夠再將他的空間擴(kuò)大一點(diǎn)變成40個(gè)字節(jié),那么這樣的話我們這里是不是就顯得更加的靈活了啊,那么我們?nèi)绾螌?shí)現(xiàn)擴(kuò)容的功能呢?那么這里我們的c語言就給了這么一個(gè)函數(shù)realloc他的功能就是擴(kuò)容用的,那么我們來看看這個(gè)函數(shù)的基本的介紹:
我們可以看到我們這個(gè)函數(shù)的需要兩個(gè)參數(shù),一個(gè)是指針,另外一個(gè)就是整型,那么這里的指針就是你想要擴(kuò)容的地址,那么這個(gè)地址跟我們free函數(shù)的要求是一樣的,得是你申請這個(gè)內(nèi)存的起始地址,不能是中間的某個(gè)地址,那么如果我們擴(kuò)容成功的話我們這里返回的就是新開辟內(nèi)存的起始地址,如果沒有開辟成功的話就返回的是空指針,那么我們這里就可以看看下面的代碼:
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> int main() { int* p = (int *)malloc(40); if (p == NULL) { printf("%s", strerror(errno)); return 1; } int i = 0; for (i = 0; i < 10; i++) { p[i] = i; } for(i = 0; i < 10; i++) { printf("%d ", p[i]); } int* p1 = (int *)realloc(p, 80); if (p == NULL) { printf("%s", strerror(errno)); return 1; } p = p1; p1 = NULL; for (i = 0; i < 10; i++) { p[i + 10] = i + 11; } printf("\n"); for (i = 0; i < 20; i++) { printf("%d ", p[i]); } free(p); p = NULL; return 0; } }
那么我們這里來看看這段代碼的運(yùn)行結(jié)果為:
那么我們這里就可以看到我們這里卻是將我們的內(nèi)存進(jìn)行了擴(kuò)展,我們這里一下子就能夠容納下20個(gè)整型,那么這里大家仔細(xì)觀察我們的代碼就可以看到的就是我們這里擴(kuò)容的時(shí)候是先創(chuàng)建一個(gè)變量來接收我們這里擴(kuò)容之后的返回值,那么我們這里因?yàn)榉祷氐闹悼赡芸罩羔?,所以我們這里為了防止這里因?yàn)閿U(kuò)容失敗而導(dǎo)致我們原來的開辟的內(nèi)存的地址丟失,就先來創(chuàng)建一個(gè)變量來試試水,下面的if語句就是用來判斷的過程判斷他是否開辟成功如果開辟成功的話我們才對其進(jìn)行賦值,然后再將這個(gè)變量初始化為0,那么這里我們還有一個(gè)小點(diǎn)就是我們這里這里的realloc可以傳空指針,如果我們這里傳空指針的話那么我們這里的realloc的作用就是跟我們這里的malloc的作用一模一樣了,那么看到這里想必大家應(yīng)該直到如何來進(jìn)行擴(kuò)容了,那么我們這里就再來了解一個(gè)小點(diǎn)就是我們這里堆區(qū)的使用規(guī)則是和我們的棧區(qū)是不相同的,我們的棧區(qū)它是從地址高的往地址低的方向進(jìn)行使用,但是我們的堆區(qū)就沒有這個(gè)規(guī)則他是隨便的使用,那么這樣的話我們這里就出現(xiàn)了一個(gè)問題就是我們這里要是頻繁的使用我們的malloc等函數(shù)來開辟空間的話,那么我們這里的空間是不是就變得碎片化了起來,每隔幾個(gè)空的內(nèi)存就會(huì)有一個(gè)被占用的內(nèi)存,那么如果是這樣的化我們要是開辟一個(gè)大型的空間,那么所能夠容納下這個(gè)空間地方是不是就越來越少了啊,那么這樣就會(huì)導(dǎo)致我們空間利用率較低,而且我們這里開辟的空間并不是這個(gè)函數(shù)來開辟的,而是這個(gè)函數(shù)來調(diào)用我們的操作系統(tǒng)的接口來申請的空間,那么如果是這樣的話那么我們每調(diào)用一次這個(gè)函數(shù),都會(huì)像我們的操作系統(tǒng)來進(jìn)行申請的話,這樣我們的效率是不是也就降低了很多啊,也就浪費(fèi)了很多的時(shí)間,所以這里也是我們動(dòng)態(tài)開辟內(nèi)存的一個(gè)缺點(diǎn),那么這里我們就可以來講講我們這里我們的realloc擴(kuò)容空間的兩種情況,第一種就是我原本申請的那個(gè)空間后面的空間充足沒有被其他的東西所占用,那么這種情況我們要進(jìn)行擴(kuò)容的話,我們會(huì)直接將后面的空間劃分給你,你就可以直接使用了,但是如果你一次性擴(kuò)容的非常大的話,后面的空間中有被其他人占用的話,那么這個(gè)時(shí)候你是不能直接把別人趕出去把這一大塊空間劃分給你的,你只能自己找一個(gè)更大的空間來容納下你自己,并且還將自己原本的數(shù)據(jù)拷貝過去并且釋放掉你原來的空間,那么這里我們就可以通過代碼來驗(yàn)證一下:
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(40); p = realloc(p, 8000); return 0; }
在擴(kuò)容前我們的p的內(nèi)容為:
但是我們擴(kuò)容之后我們的地址卻變?yōu)椋?/p>
那么這里我們p的內(nèi)容發(fā)生了改變,那么我們將這里的擴(kuò)容改小一點(diǎn)我們就可以發(fā)現(xiàn)我們這里的地址在擴(kuò)容前后沒有發(fā)生改變,擴(kuò)容前:
擴(kuò)容后:
那么這里就證明我們擴(kuò)容時(shí)的兩種不同的情況。
柔性數(shù)組
也許你從來沒有聽說過柔性數(shù)組這個(gè)概念,但是它確實(shí)時(shí)存在的,在c99中,結(jié)構(gòu)體中的最后一個(gè)元素允許時(shí)位置大小的數(shù)組,那么這就叫做柔性數(shù)組的成員比如說下面的代碼:
typedef struct st_type1 { int i; int a[0]; }type_a; typedef struct st_type2 { int i; int a[]; }type_a;
我們這里創(chuàng)建的就是兩個(gè)柔性數(shù)組,但是我們這里的第一個(gè)柔性數(shù)組在有些編譯器上跑不過去,所以我們一般都采用第二種柔性數(shù)組的創(chuàng)建方法,那么我們的柔性數(shù)組就有這么幾個(gè)特征:
- 結(jié)構(gòu)中的柔性數(shù)組成員前面必須至少有一個(gè)其他的成員。
- sizeof返回的這種結(jié)構(gòu)大小不包括柔性數(shù)組的內(nèi)存
- 包含柔性數(shù)組成員的結(jié)構(gòu)得用malloc函數(shù)來進(jìn)行內(nèi)存的動(dòng)態(tài)分配,并且分配的內(nèi)存應(yīng)該大于結(jié)構(gòu)的大小。
比如說下面的代碼
#include<stdio.h> typedef struct st_type { int i; int a[0]; }type_a; int main() { printf("%d", sizeof(type_a)); return 0; }
我們這里像打印這個(gè)結(jié)構(gòu)體的大小,但是我們將這個(gè)代碼運(yùn)行的時(shí)候就會(huì)發(fā)現(xiàn)我們這里打印的結(jié)果是:
是單獨(dú)的一個(gè)4就是我們這里結(jié)構(gòu)體中整型的大小,那么這是因?yàn)槲覀冞@里的第二點(diǎn):sizeof返回的這種結(jié)構(gòu)大小不包括柔性數(shù)組的內(nèi)存,那么我們的柔性數(shù)組又該如何來使用呢?那么我們這里就說啊要想使用柔性數(shù)組就得用malloc來對其進(jìn)行,并且我們對其分配的大小應(yīng)該大于我們的sizeof求得的大小,那么這里多出來的空間就是給我們這里的柔性數(shù)組的,比如說下面的代碼:
#include<stdio.h> #include<stdlib.h> struct s { int i; int a[0]; }; int main() { struct s* ps = (struct s *)malloc(sizeof(struct s) + 40); ps->i = 100; int i = 0; for (i = 0; i < 10; i++) { ps->a[i] = i; } for (i = 0; i < 10; i++) { printf("%d ", ps->a[i]); } return 0; }
那么這里就是我們?nèi)嵝詳?shù)組的使用,我們先用該結(jié)構(gòu)創(chuàng)建出來一個(gè)指針,然后再用malloc來開辟一個(gè)空間,那么這里開辟的空間的大小就是
sizeof(struct s) + 40,那么這里多出來的40就是給我們這里的數(shù)組的大小,然后我們的數(shù)組的每個(gè)元素的類型就是int類型,那么我們這里就相當(dāng)于這個(gè)數(shù)組有10個(gè)元素,那么我們這里就可以對這個(gè)數(shù)組了來進(jìn)行一個(gè)一個(gè)的賦值,并且打印那么我們這里的代碼如下:
但是我們這里叫的是柔性數(shù)組,說明我們這里是可以來進(jìn)行修改的,所以當(dāng)我們感覺這個(gè)數(shù)組的大小要是不夠用的話我們這里就可以使用realloc來對其進(jìn)行擴(kuò)容,那么我們這里的代碼就如下:
#include<stdio.h> #include<stdlib.h> struct s { int i; int a[0]; }; int main() { struct s* ps = (struct s *)malloc(sizeof(struct s) + 40); if (ps == NULL) { return 1; } ps->i = 100; int i = 0; for (i = 0; i < 10; i++) { ps->a[i] = i; } for (i = 0; i < 10; i++) { printf("%d ", ps->a[i]); } struct s *ps1 =(struct s*)realloc(ps, sizeof(struct s) + 80); if (ps1 != NULL) { ps = ps1; ps1 = NULL; } free(ps); ps = NULL; return 0; }
那么我們這里的代碼就是將這個(gè)這里的內(nèi)容進(jìn)行擴(kuò)容,然后在對其進(jìn)行釋放。但是看到這里有些小伙伴們就有了這么一個(gè)疑問說為什么非要用柔性數(shù)組呢?如果說為了實(shí)現(xiàn)大小變大的話,我們完全可以這么做啊,就是創(chuàng)建一個(gè)結(jié)構(gòu)體里面裝一個(gè)整型和一個(gè)指針,這個(gè)整型我們就跟上面的一樣用來存儲(chǔ)數(shù)據(jù),而我們的指針就是指向一個(gè)數(shù)組的首元素的地址,然后為了統(tǒng)一我們這里就可以使用malloc來講這個(gè)結(jié)構(gòu)體創(chuàng)建在堆區(qū)里面,然后我們再用里面在堆區(qū)里面創(chuàng)建一塊地址,講這個(gè)地址中的首元素的地址賦值給我們的這里結(jié)構(gòu)體中的那個(gè)指針不也可以實(shí)現(xiàn)上面柔性數(shù)組的功能嗎?對吧也可以增大擴(kuò)容啊,那么我們講上面的思路轉(zhuǎn)換成代碼就是這樣:
#include<stdio.h> struct s { int a; int* arr; }; int main() { struct s* ps = (struct s*)malloc(sizeof(struct s)); if (ps == NULL) { return 1; } ps->a = 100; ps->arr = (int*)malloc; if (ps->arr = NULL) { //.... return 1; } //使用 int i = 0; for (i = 0; i < 10; i++) { ps->arr[i] = i; } for (i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } //擴(kuò)容 int* ptr = (int*)realloc(ps->arr, 80); if (ptr == NULL) { return 1; } //使用 // //釋放 free(ps->arr); free(ps); ps = NULL; return 0; }
那么我們的柔性數(shù)組相對于這個(gè)方式就有兩大優(yōu)點(diǎn):
第一個(gè)好處是:方便內(nèi)存釋放
如果我們的代碼是在一個(gè)給別人用的函數(shù)中,你在里面做了二次內(nèi)存分配,并把整個(gè)結(jié)構(gòu)體返回給用戶。用戶調(diào)用free可以釋放結(jié)構(gòu)體,但是用戶并不知道這個(gè)結(jié)構(gòu)體內(nèi)的成員也需要free,所以你不能指望用戶來發(fā)現(xiàn)這個(gè)事。所以,如果我們把結(jié)構(gòu)體的內(nèi)存以及其成員要的內(nèi)存一次性分配好了,并返回給用戶一個(gè)結(jié)構(gòu)體指針,用戶做一次free就可以把所有的內(nèi)存也給釋放掉。
第二個(gè)好處是:
這樣有利于訪問速度.連續(xù)的內(nèi)存有益于提高訪問速度,也有益于減少內(nèi)存碎片。(其實(shí),我個(gè)人覺得也沒多高了,反正你跑不了要用做偏移量的加法來尋址)
到此這篇關(guān)于C語言動(dòng)態(tài)內(nèi)存的分配最全面分析的文章就介紹到這了,更多相關(guān)c語言動(dòng)態(tài)內(nèi)存分配內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 深入了解C語言的動(dòng)態(tài)內(nèi)存管理
- 詳解C語言中動(dòng)態(tài)內(nèi)存管理及柔性數(shù)組的使用
- 一文帶你搞懂C語言動(dòng)態(tài)內(nèi)存管理
- 詳解C語言中的動(dòng)態(tài)內(nèi)存管理
- C語言動(dòng)態(tài)內(nèi)存分配圖文講解
- 使用c語言輕松實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存管
- 一文帶你了解C語言中的動(dòng)態(tài)內(nèi)存管理函數(shù)
- C語言動(dòng)態(tài)內(nèi)存管理的原理及實(shí)現(xiàn)方法
- 詳解C語言中動(dòng)態(tài)內(nèi)存管理
- C語言中常見的六種動(dòng)態(tài)內(nèi)存錯(cuò)誤總結(jié)
- 一文解析C語言中動(dòng)態(tài)內(nèi)存管理
- C語言動(dòng)態(tài)內(nèi)存管理的實(shí)現(xiàn)示例
相關(guān)文章
C語言實(shí)現(xiàn)簡單班級(jí)成績管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)簡單班級(jí)成績管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03一篇文章教你自己動(dòng)手實(shí)現(xiàn)C語言庫函數(shù)
這篇文章主要介紹了C語言庫函數(shù)的相關(guān)資料,小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2021-09-09C++學(xué)習(xí)之移動(dòng)語義與智能指針詳解
智能指針和移動(dòng)語義是迄今為止,最難理解的兩個(gè)概念,下面這篇文章主要給大家介紹了關(guān)于C++學(xué)習(xí)之移動(dòng)語義與智能指針的相關(guān)資料,需要的朋友可以參考下2021-05-05C++實(shí)現(xiàn)LeetCode(57.插入?yún)^(qū)間)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(57.插入?yún)^(qū)間),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C語言中#define與typedef的互換細(xì)節(jié)詳解
本篇文章是對C語言中#define與typedef的互換細(xì)節(jié)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C語言之結(jié)構(gòu)體定義 typedef struct 用法詳解和用法小結(jié)
這篇文章主要介紹了C語言的結(jié)構(gòu)體定義typedef struct用法詳解和用法小結(jié),typedef是類型定義,typedef struct 是為了使用這個(gè)結(jié)構(gòu)體方便,感興趣的同學(xué)可以參考閱讀2023-03-03c++ 構(gòu)造函數(shù)中調(diào)用虛函數(shù)的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄猚++ 構(gòu)造函數(shù)中調(diào)用虛函數(shù)的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12深入C/C++浮點(diǎn)數(shù)在內(nèi)存中的存儲(chǔ)方式詳解
本篇文章是對C/C++浮點(diǎn)數(shù)在內(nèi)存中的存儲(chǔ)方式進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05