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