malloc和free——结构体中动态内存的管理
生活随笔
收集整理的這篇文章主要介紹了
malloc和free——结构体中动态内存的管理
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
C語言中內(nèi)存的管理主要是依據(jù)malloc和free實現(xiàn)的,其中malloc主要是實現(xiàn)內(nèi)存的分配,而free則是實現(xiàn)內(nèi)存的釋放。雖然這是我們已經(jīng)很熟悉的,但是還是存在一些問題。特別是當(dāng)結(jié)構(gòu)體中存在指針的情況下,各種問題也就會展現(xiàn)出來。
其中最大的問題是:結(jié)構(gòu)體中指針變量沒有指向一塊合法的內(nèi)存空間,就對指針參數(shù)進(jìn)行操作,這也是很多C語言程序員經(jīng)常犯的錯誤。簡單的實例如下:struct student {char *name;int score; }stu,*pstu;int main() {strcpy(stu.name,"Jimy");stu.score = 99;strcpy(pstu->name,"Jimy");pstu->score = 99; } 這種代碼一般都會編譯通過,但是運行過程中會發(fā)生新手編程經(jīng)常出現(xiàn)的段錯誤Segmentation fault (core dumped),我通過gdb對程序進(jìn)行調(diào)試發(fā)現(xiàn)了存在的一些問題。其中stu.name中的內(nèi)容是0x0,也就是地址0x0。這樣我就知道了0x0為什么會發(fā)生段錯誤了,因為在Linux中進(jìn)程都有一個獨立的虛擬存儲空間4G,但是其中在最底部的0x0是沒有映射的,具體的參看進(jìn)程的存儲器映射關(guān)系。0x0并沒有映射,這樣發(fā)生段錯誤也就不奇怪了。
也就是說指針變量里存儲的地址值并不是一個我們需要的值,為了指向一塊內(nèi)存空間,因此需要采用malloc分配一塊內(nèi)存空間。 int pnumber = (int *)malloc(75*sizeof(int)); 類型轉(zhuǎn)換(int *)將函數(shù)返回的地址轉(zhuǎn)化成int類型的指針。sizeof是一個運算符,它返回一個size_t類型的無符號的整數(shù),該整數(shù)是存儲它的參數(shù)需要的字節(jié)數(shù)。這個例子中請求分配足以存儲75個int數(shù)據(jù)項的內(nèi)存。 改寫上面的代碼實現(xiàn)內(nèi)存的分配。int main() {/*創(chuàng)建一塊內(nèi)存空間,并讓stu.name指向這塊內(nèi)存空間*/stu.name = (char *)malloc(20*sizeof(char));/*實現(xiàn)字符串的復(fù)制過程*/strcpy(stu.name,"Jimy");stu.score = 99;/*創(chuàng)建一塊內(nèi)存空間,并讓pstu指向這塊內(nèi)存空間*/pstu = (struct student *)malloc(sizeof(struct student));/*創(chuàng)建一塊內(nèi)存空間,并讓pstu->name指向這塊內(nèi)存空間*/pstu->name = (char *)malloc(20*sizeof(char));/*實現(xiàn)字符串的復(fù)制過程*/strcpy(pstu->name,"Jimy");pstu->score = 99;return 0; } 這樣補充以后的代碼就為指針變量添加了指向的對象,由于是采用malloc動態(tài)申請的存儲空間,那么這段存儲空間是分配在堆中,而不是在棧中,如果是在被調(diào)用函數(shù)中申請內(nèi)存空間,那么在函數(shù)返回后該內(nèi)存空間并不會釋放。 Breakpoint 1, main () at TestStructPoint.c:21 21 stu.name = (char *)malloc(20*sizeof(char)); Missing separate debuginfos, use: debuginfo-install glibc-2.12.90-17.i686 (gdb) p stu ----stu中的內(nèi)容 $1 = {name = 0x0, score = 0} (gdb) p stu.name ----stu.name其中的內(nèi)容是0x0,也就是指向0x0 $2 = 0x0 (gdb) c Continuing.Breakpoint 2, main () at TestStructPoint.c:25 25 strcpy(stu.name,"Jimy"); (gdb) p stu.name -----stu.name其中的內(nèi)容不再是0x0,而是一個地址值,該地值中的內(nèi)容為空 $3 = 0x804a008 "" (gdb) c Continuing.Breakpoint 3, main () at TestStructPoint.c:26 26 stu.score = 99; (gdb) p stu.name -----stu.name中存儲的地址的內(nèi)容發(fā)生了變化。 $4 = 0x804a008 "Jimy" (gdb) c Continuing.Breakpoint 4, main () at TestStructPoint.c:29 29 pstu = (struct student *)malloc(sizeof(struct student)); (gdb) p pstu ----pstu指向的地址也是0x0 $5 = (struct student *) 0x0 (gdb) c Continuing.Breakpoint 5, main () at TestStructPoint.c:32 32 pstu->name = (char *)malloc(20*sizeof(char)); (gdb) p pstu ----pstu指向的內(nèi)存地址發(fā)生了改變,不再是0x0 $6 = (struct student *) 0x804a020 (gdb) c Continuing.Breakpoint 6, main () at TestStructPoint.c:35 35 strcpy(pstu->name,"Jimy"); (gdb) p pstu $7 = (struct student *) 0x804a020 (gdb) p pstu->name ----pstu->name中的地址也不再是0x0,而是一個非零的地址值 $8 = 0x804a030 "" (gdb) c Continuing.Breakpoint 7, main () at TestStructPoint.c:36 36 pstu->score = 99; (gdb) p pstu->name $9 = 0x804a030 "Jimy" ----pstu->name指向地址中的內(nèi)容發(fā)生改變 (gdb) c Continuing.Program exited normally. 根據(jù)上面的調(diào)試可以知道,指針變量在定義過程中沒有初始化為NULL,則指針變量指向的地址就是0x0,而在Linux中的進(jìn)程虛擬存儲器映射中地址0x0并沒有映射,因此會出現(xiàn)錯誤。因此結(jié)構(gòu)體中的指針變量一定要指向一塊具體的存儲空間之后才能進(jìn)行相應(yīng)的操作。同時其他的指針也必須指向相應(yīng)的地址以后再操作。
但是分配完地址后還需要在相應(yīng)操作結(jié)束后釋放分配的存儲器,不然會造成浪費,以及內(nèi)存的泄漏。這也是很多程序員忘記完成的工作。
內(nèi)存的釋放采用free函數(shù)即可,free函數(shù)是將分配的這塊內(nèi)存與指針(malloc返回的指針)之間的所有關(guān)系斬斷,指針變量P中存儲的地址(這塊內(nèi)存的起始地址)值也沒有發(fā)生變化,同時存儲器中存儲的內(nèi)容也并沒有發(fā)生改變,改變的只是指針對這塊內(nèi)存地址的所有權(quán)問題。但是該起始地址所在內(nèi)存中的數(shù)據(jù)內(nèi)容已經(jīng)沒法使用了,即時采用其他的指針也不能訪問。如果下一次調(diào)用malloc函數(shù)可能會在剛才釋放的區(qū)域創(chuàng)建一個內(nèi)存空間,由于釋放以后的存儲空間的內(nèi)容并沒有改變(我是參考書上的,但我認(rèn)為free后存儲器中的內(nèi)容是發(fā)生變化的,后面的調(diào)試可以說明這個問題,只是不知道發(fā)生什么變化,我也只是猜測,但是不要訪問這個存儲空間的內(nèi)容是最安全的),這樣可能會影響后面的結(jié)果,因此需要對創(chuàng)建的內(nèi)存空間進(jìn)行清零操作(防止前面的操作影響后面),這通常采用memset函數(shù)實現(xiàn),具體參看memset函數(shù)。還有指針變量P中存儲的地址值并沒有改變,由于指針P沒有對這個地址的訪問權(quán)限,程序中對P的引用都可能導(dǎo)致錯誤的產(chǎn)生,造成野指針,因此最后還需要將指針P指向NULL,避免野指針的產(chǎn)生。當(dāng)然也需要對創(chuàng)建是否成功需要檢測,但這里我暫時不考慮這些錯誤的處理。 #include<stdio.h> #include<stdlib.h> #include<string.h>struct student {char *name;int score; }stu,*pstu;int main() {/*為name分配指向的一段內(nèi)存空間*/stu.name = (char *)malloc(20*sizeof(char));memset(stu.name,0,20*sizeof(char));strcpy(stu.name,"Jimy");stu.score = 99;/*為pstu分配指向的一段內(nèi)存空間*/pstu = (struct student *)malloc(sizeof(struct student));memset(pstu,0,sizeof(struct student));/*為name分配指向的一段內(nèi)存空間*/pstu->name = (char *)malloc(20*sizeof(char));memset(pstu->name,0,20*sizeof(char));strcpy(pstu->name,"Jimy");pstu->score = 99;/*采用另外的指針訪問分配的存儲空間,測試內(nèi)存中內(nèi)容是否改變*/char *p = stu.name;char *p1 = (char *)0x804a008;//具體的地址值 char *ppstu = pstu->name;char *pp = (char *)0x804a030;//具體的地址值/*釋放的順序要注意,pstu->name必須在pstu釋放之前釋放,*如果pstu先釋放,那么pstu->name就不能正確的訪問。*/free(pstu->name);free(stu.name);free(pstu);/*為了防止野指針產(chǎn)生*/pstu->name = NULL;stu.name = NULL;pstu = NULL;p = NULL;ppstu = NULL;return 0; } 下面的全部是調(diào)試結(jié)果,根據(jù)調(diào)試結(jié)果說明問題:(gdb) r Starting program: /home/gong/program/cprogram/TestStructPoint Breakpoint 1, main () at TestStructPoint.c:14 14 stu.name = (char *)malloc(20*sizeof(char)); Missing separate debuginfos, use: debuginfo-install glibc-2.12.90-17.i686 (gdb) p stu $1 = {name = 0x0, score = 0} (gdb) p stu.name $2 = 0x0 (gdb) c Continuing.Breakpoint 2, main () at TestStructPoint.c:17 17 strcpy(stu.name,"Jimy"); (gdb) p stu.name $3 = 0x804a008 "" (gdb) c Continuing.Breakpoint 3, main () at TestStructPoint.c:21 21 pstu = (struct student *)malloc(sizeof(struct student)); (gdb) p stu.name $4 = 0x804a008 "Jimy" (gdb) p stu $5 = {name = 0x804a008 "Jimy", score = 99} (gdb) p pstu $6 = (struct student *) 0x0 (gdb) c Continuing.Breakpoint 4, main () at TestStructPoint.c:24 24 pstu->name = (char *)malloc(20*sizeof(char)); (gdb) p pstu $7 = (struct student *) 0x804a020 (gdb) p pstu->name $8 = 0x0 (gdb) c Continuing.Breakpoint 5, main () at TestStructPoint.c:27 27 strcpy(pstu->name,"Jimy"); (gdb) p pstu->name $9 = 0x804a030 "" (gdb) c Continuing.Breakpoint 6, main () at TestStructPoint.c:31 31 char *p = stu.name; (gdb) p pstu->name $10 = 0x804a030 "Jimy" (gdb) p *pstu $11 = {name = 0x804a030 "Jimy", score = 99} (gdb) p p $12 = 0x854ff4 "|M\205" (gdb) c Continuing.Breakpoint 7, main () at TestStructPoint.c:32 32 char *p1 = (char *)0x804a008;//具體的地址值 (gdb) p p1 $13 = 0x855ca0 "" (gdb) c Continuing.Breakpoint 8, main () at TestStructPoint.c:33 33 char *ppstu = pstu->name; (gdb) p p1 $14 = 0x804a008 "Jimy" (gdb) p ppstu $15 = 0x855ca0 "" (gdb) c Continuing.Breakpoint 9, main () at TestStructPoint.c:34 34 char *pp = (char *)0x804a030;//具體的地址值 (gdb) p ppstu $16 = 0x804a030 "Jimy" (gdb) p pp $17 = 0x804826a "__libc_start_main" (gdb) c Continuing.Breakpoint 10, main () at TestStructPoint.c:37 37 free(pstu->name); (gdb) p pp $18 = 0x804a030 "Jimy" (gdb) p pstu->name $19 = 0x804a030 "Jimy" (gdb) c Continuing.Breakpoint 11, main () at TestStructPoint.c:38 38 free(stu.name); (gdb) p pstu->name $20 = 0x804a030 "" (gdb) c Continuing.Breakpoint 12, main () at TestStructPoint.c:39 39 free(pstu); (gdb) p stu.name $21 = 0x804a008 "(\240\004\b" (gdb) p pstu $22 = (struct student *) 0x804a020 (gdb) p *pstu $23 = {name = 0x804a030 "", score = 99} (gdb) c Continuing.Breakpoint 13, main () at TestStructPoint.c:41 41 pstu->name = NULL; (gdb) p *pstu $24 = {name = 0x0, score = 99} (gdb) p pstu->name $25 = 0x0 (gdb) c Continuing.Breakpoint 14, main () at TestStructPoint.c:47 47 return 0; (gdb) p p1 $26 = 0x804a008 "(\240\004\b" (gdb) p pp $27 = 0x804a030 "" (gdb) p pstu $28 = (struct student *) 0x0 (gdb) p pstu->name Cannot access memory at address 0x0 (gdb) 具體的調(diào)試過程說明了其中很多的問題,根據(jù)其中的結(jié)果可以知道,free結(jié)束以后指針變量P(malloc返回)中存儲的地址值并沒有改變,但是通過該地值已經(jīng)不能訪問之前的分配的存儲空間。我采用其他的指針(直接賦值地址以及指針賦值)訪問得到的結(jié)果也不是正確的值,雖然這不能說明地址中的數(shù)據(jù)改變了,但說明對釋放以后的存儲空間再通過其他方法訪問不會得到正確的值,但是內(nèi)存空間中存在值,并不一定是0,因此每一次malloc都清零是必要的。防止野指針也是非常必要的,減少程序錯誤的概率。
最后說明一下,鏈表作為結(jié)構(gòu)體的衍生產(chǎn)物,鏈表的結(jié)構(gòu)體中就有指針變量,因此一定草采用malloc等分配好內(nèi)存塊以后,再對鏈表進(jìn)行操作,不然都會導(dǎo)致不同問題的產(chǎn)生。
free函數(shù)的形參是void *類型,所有的指針類型都可以自動轉(zhuǎn)換為這個類型,所以可以把任意類型的指針做為參數(shù)傳送給這個函數(shù),
原文鏈接:http://blog.chinaunix.net/uid-20937170-id-3016394.html
總結(jié)
以上是生活随笔為你收集整理的malloc和free——结构体中动态内存的管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: debug —— C语言 编译时
- 下一篇: 对C语言 结构体 和 结构变量