C語(yǔ)言中內(nèi)存的管理主要是依據(jù)malloc和free實(shí)現(xiàn)的,其中malloc主要是實(shí)現(xiàn)內(nèi)存的分配,而free則是實(shí)現(xiàn)內(nèi)存的釋放。雖然這是我們已經(jīng)很熟悉的,但是還是存在一些問(wèn)題。特別是當(dāng)結(jié)構(gòu)體中存在指針的情況下,各種問(wèn)題也就會(huì)展現(xiàn)出來(lái)。
其中最大的問(wèn)題是:結(jié)構(gòu)體中指針變量沒(méi)有指向一塊合法的內(nèi)存空間,就對(duì)指針參數(shù)進(jìn)行操作,這也是很多C語(yǔ)言程序員經(jīng)常犯的錯(cuò)誤。
簡(jiǎn)單的實(shí)例如下:
struct student
{
char *name;
int score;
}stu,*pstu;
int main()
{
strcpy(stu.name,"Jimy");
stu.score = 99;
strcpy(pstu->name,"Jimy");
pstu->score = 99;
}
這種代碼是新手經(jīng)常犯的錯(cuò)誤,其中的主要錯(cuò)誤是指針變量沒(méi)有指向一塊內(nèi)存空間,其中包括ptest沒(méi)有指向一塊內(nèi)存空間,同時(shí)結(jié)構(gòu)體中的指針變量name也沒(méi)有指向一塊內(nèi)存空間。
這種代碼一般都會(huì)編譯通過(guò),但是運(yùn)行過(guò)程中會(huì)發(fā)生新手編程經(jīng)常出現(xiàn)的段錯(cuò)誤Segmentation fault (core dumped),我通過(guò)gdb對(duì)程序進(jìn)行調(diào)試發(fā)現(xiàn)了存在的一些問(wèn)題。其中stu.name中的內(nèi)容是0x0,也就是地址0x0。這樣我就知道了0x0為什么會(huì)發(fā)生段錯(cuò)誤了,因?yàn)樵贚inux中進(jìn)程都有一個(gè)獨(dú)立的虛擬存儲(chǔ)空間4G,但是其中在最底部的0x0是沒(méi)有映射的,具體的參看進(jìn)程的存儲(chǔ)器映射關(guān)系。0x0并沒(méi)有映射,這樣發(fā)生段錯(cuò)誤也就不奇怪了。
也就是說(shuō)指針變量里存儲(chǔ)的地址值并不是一個(gè)我們需要的值,為了指向一塊內(nèi)存空間,因此需要采用malloc分配一塊內(nèi)存空間。
改寫上面的代碼實(shí)現(xiàn)內(nèi)存的分配。
int main()
{
/*創(chuàng)建一塊內(nèi)存空間,并讓stu.name指向這塊內(nèi)存空間*/
stu.name = (char *)malloc(20*sizeof(char));
/*實(shí)現(xiàn)字符串的復(fù)制過(guò)程*/
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));
/*實(shí)現(xiàn)字符串的復(fù)制過(guò)程*/
strcpy(pstu->name,"Jimy");
pstu->score = 99;
return 0;
}
這樣補(bǔ)充以后的代碼就為指針變量添加了指向的對(duì)象,由于是采用malloc動(dòng)態(tài)申請(qǐng)的存儲(chǔ)空間,那么這段存儲(chǔ)空間是分配在堆中,而不是在棧中,如果是在被調(diào)用函數(shù)中申請(qǐng)內(nèi)存空間,那么在函數(shù)返回后該內(nèi)存空間并不會(huì)釋放。
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,而是一個(gè)地址值,該地值中的內(nèi)容為空
$3 = 0x804a008 ""
(gdb) c
Continuing.
Breakpoint 3, main () at TestStructPoint.c:26
26 stu.score = 99;
(gdb) p stu.name -----stu.name中存儲(chǔ)的地址的內(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,而是一個(gè)非零的地址值
$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)試可以知道,指針變量在定義過(guò)程中沒(méi)有初始化為NULL,則指針變量指向的地址就是0x0,而在Linux中的進(jìn)程虛擬存儲(chǔ)器映射中地址0x0并沒(méi)有映射,因此會(huì)出現(xiàn)錯(cuò)誤。因此結(jié)構(gòu)體中的指針變量一定要指向一塊具體的存儲(chǔ)空間之后才能進(jìn)行相應(yīng)的操作。同時(shí)其他的指針也必須指向相應(yīng)的地址以后再操作。
但是分配完地址后還需要在相應(yīng)操作結(jié)束后釋放分配的存儲(chǔ)器,不然會(huì)造成浪費(fèi),以及內(nèi)存的泄漏。這也是很多程序員忘記完成的工作。
內(nèi)存的釋放采用free函數(shù)即可,free函數(shù)是將分配的這塊內(nèi)存與指針(malloc返回的指針)之間的所有關(guān)系斬?cái),指針變量P中存儲(chǔ)的地址(這塊內(nèi)存的起始地址)值也沒(méi)有發(fā)生變化,同時(shí)存儲(chǔ)器中存儲(chǔ)的內(nèi)容也并沒(méi)有發(fā)生改變,改變的只是指針對(duì)這塊內(nèi)存地址的所有權(quán)問(wèn)題。但是該起始地址所在內(nèi)存中的數(shù)據(jù)內(nèi)容已經(jīng)沒(méi)法使用了,即時(shí)采用其他的指針也不能訪問(wèn)。如果下一次調(diào)用malloc函數(shù)可能會(huì)在剛才釋放的區(qū)域創(chuàng)建一個(gè)內(nèi)存空間,由于釋放以后的存儲(chǔ)空間的內(nèi)容并沒(méi)有改變(我是參考書上的,但我認(rèn)為free后存儲(chǔ)器中的內(nèi)容是發(fā)生變化的,后面的調(diào)試可以說(shuō)明這個(gè)問(wèn)題,只是不知道發(fā)生什么變化,我也只是猜測(cè),但是不要訪問(wèn)這個(gè)存儲(chǔ)空間的內(nèi)容是最安全的),這樣可能會(huì)影響后面的結(jié)果,因此需要對(duì)創(chuàng)建的內(nèi)存空間進(jìn)行清零操作(防止前面的操作影響后面),這通常采用memset函數(shù)實(shí)現(xiàn),具體參看memset函數(shù)。還有指針變量P中存儲(chǔ)的地址值并沒(méi)有改變,由于指針P沒(méi)有對(duì)這個(gè)地址的訪問(wèn)權(quán)限,程序中對(duì)P的引用都可能導(dǎo)致錯(cuò)誤的產(chǎn)生,造成野指針,因此最后還需要將指針P指向NULL,避免野指針的產(chǎn)生。當(dāng)然也需要對(duì)創(chuàng)建是否成功需要檢測(cè),但這里我暫時(shí)不考慮這些錯(cuò)誤的處理。
#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;
/*采用另外的指針訪問(wèn)分配的存儲(chǔ)空間,測(cè)試內(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就不能正確的訪問(wèn)。
*/
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é)果說(shuō)明問(wèn)題:
(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)試過(guò)程說(shuō)明了其中很多的問(wèn)題,根據(jù)其中的結(jié)果可以知道,free結(jié)束以后指針變量P(malloc返回)中存儲(chǔ)的地址值并沒(méi)有改變,但是通過(guò)該地值已經(jīng)不能訪問(wèn)之前的分配的存儲(chǔ)空間。我采用其他的指針(直接賦值地址以及指針賦值)訪問(wèn)得到的結(jié)果也不是正確的值,雖然這不能說(shuō)明地址中的數(shù)據(jù)改變了,但說(shuō)明對(duì)釋放以后的存儲(chǔ)空間再通過(guò)其他方法訪問(wèn)不會(huì)得到正確的值,但是內(nèi)存空間中存在值,并不一定是0,因此每一次malloc都清零是必要的。防止野指針也是非常必要的,減少程序錯(cuò)誤的概率。
最后說(shuō)明一下,鏈表作為結(jié)構(gòu)體的衍生產(chǎn)物,鏈表的結(jié)構(gòu)體中就有指針變量,因此一定草采用malloc等分配好內(nèi)存塊以后,再對(duì)鏈表進(jìn)行操作,不然都會(huì)導(dǎo)致不同問(wèn)題的產(chǎn)生。