其中最大的问题是:结构体中指针变量没有指向一块合法的内存空间,就对指针参数进行操作,这也是很多C语言程序员经常犯的错误。
简单的实例如下:
struct student
{
char *name;
int score;
}stu,*pstu;
int main()
{
strcpy(stu.name,"Jimy");
stu.score = 99;
strcpy(pstu->name,"Jimy");
pstu->score = 99;
}
这种代码是新手经常犯的错误,其中的主要错误是指针变量没有指向一块内存空间,其中包括ptest没有指向一块内存空间,同时结构体中的指针变量name也没有指向一块内存空间。
这种代码一般都会编译通过,但是运行过程中会发生新手编程经常出现的段错误Segmentation fault (core dumped),我通过gdb对程序进行调试发现了存在的一些问题。其中stu.name中的内容是0x0,也就是地址0x0。这样我就知道了0x0为什么会发生段错误了,因为在Linux中进程都有一个独立的虚拟存储空间4G,但是其中在最底部的0x0是没有映射的,具体的参看进程的存储器映射关系。0x0并没有映射,这样发生段错误也就不奇怪了。
也就是说指针变量里存储的地址值并不是一个我们需要的值,为了指向一块内存空间,因此需要采用malloc分配一块内存空间。
改写上面的代码实现内存的分配。
int main()
{
/*创建一块内存空间,并让stu.name指向这块内存空间*/
stu.name = (char *)malloc(20*sizeof(char));
/*实现字符串的复制过程*/
strcpy(stu.name,"Jimy");
stu.score = 99;
/*创建一块内存空间,并让pstu指向这块内存空间*/
pstu = (struct student *)malloc(sizeof(struct student));
/*创建一块内存空间,并让pstu->name指向这块内存空间*/
pstu->name = (char *)malloc(20*sizeof(char));
/*实现字符串的复制过程*/
strcpy(pstu->name,"Jimy");
pstu->score = 99;
return 0;
}
这样补充以后的代码就为指针变量添加了指向的对象,由于是采用malloc动态申请的存储空间,那么这段存储空间是分配在堆中,而不是在栈中,如果是在被调用函数中申请内存空间,那么在函数返回后该内存空间并不会释放。
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中的内容
$1 = {name = 0x0, score = 0}
(gdb) p stu.name ----stu.name其中的内容是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其中的内容不再是0x0,而是一个地址值,该地值中的内容为空
$3 = 0x804a008 ""
(gdb) c
Continuing.
Breakpoint 3, main () at TestStructPoint.c:26
26 stu.score = 99;
(gdb) p stu.name -----stu.name中存储的地址的内容发生了变化。
$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指向的内存地址发生了改变,不再是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指向地址中的内容发生改变
(gdb) c
Continuing.
Program exited normally.
根据上面的调试可以知道,指针变量在定义过程中没有初始化为NULL,则指针变量指向的地址就是0x0,而在Linux中的进程虚拟存储器映射中地址0x0并没有映射,因此会出现错误。因此结构体中的指针变量一定要指向一块具体的存储空间之后才能进行相应的操作。同时其他的指针也必须指向相应的地址以后再操作。
但是分配完地址后还需要在相应操作结束后释放分配的存储器,不然会造成浪费,以及内存的泄漏。这也是很多程序员忘记完成的工作。
内存的释放采用free函数即可,free函数是将分配的这块内存与指针(malloc返回的指针)之间的所有关系斩断,指针变量P中存储的地址(这块内存的起始地址)值也没有发生变化,同时存储器中存储的内容也并没有发生改变,改变的只是指针对这块内存地址的所有权问题。但是该起始地址所在内存中的数据内容已经没法使用了,即时采用其他的指针也不能访问。如果下一次调用malloc函数可能会在刚才释放的区域创建一个内存空间,由于释放以后的存储空间的内容并没有改变(我是参考书上的,但我认为free后存储器中的内容是发生变化的,后面的调试可以说明这个问题,只是不知道发生什么变化,我也只是猜测,但是不要访问这个存储空间的内容是最安全的),这样可能会影响后面的结果,因此需要对创建的内存空间进行清零操作(防止前面的操作影响后面),这通常采用memset函数实现,具体参看memset函数。还有指针变量P中存储的地址值并没有改变,由于指针P没有对这个地址的访问权限,程序中对P的引用都可能导致错误的产生,造成野指针,因此最后还需要将指针P指向NULL,避免野指针的产生。当然也需要对创建是否成功需要检测,但这里我暂时不考虑这些错误的处理。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct student
{
char *name;
int score;
}stu,*pstu;
int main()
{
/*为name分配指向的一段内存空间*/
stu.name = (char *)malloc(20*sizeof(char));
memset(stu.name,0,20*sizeof(char));
strcpy(stu.name,"Jimy");
stu.score = 99;
/*为pstu分配指向的一段内存空间*/
pstu = (struct student *)malloc(sizeof(struct student));
memset(pstu,0,sizeof(struct student));
/*为name分配指向的一段内存空间*/
pstu->name = (char *)malloc(20*sizeof(char));
memset(pstu->name,0,20*sizeof(char));
strcpy(pstu->name,"Jimy");
pstu->score = 99;
/*采用另外的指针访问分配的存储空间,测试内存中内容是否改变*/
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);
/*为了防止野指针产生*/
pstu->name = NULL;
stu.name = NULL;
pstu = NULL;
p = NULL;
ppstu = NULL;
return 0;
}
下面的全部是调试结果,根据调试结果说明问题:
(gdb) r
Starting program: /home/gong/program/cprogram/TestStructPoint
Breakpoint 1, main () at TestStructPoint.c:14
14stu.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
17strcpy(stu.name,"Jimy");
(gdb) p stu.name
$3 = 0x804a008 ""
(gdb) c
Continuing.
Breakpoint 3, main () at TestStructPoint.c:21
21pstu = (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
24pstu->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
27strcpy(pstu->name,"Jimy");
(gdb) p pstu->name
$9 = 0x804a030 ""
(gdb) c
Continuing.
Breakpoint 6, main () at TestStructPoint.c:31
31char *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
32char *p1 = (char *)0x804a008;//具体的地址值
(gdb) p p1
$13 = 0x855ca0 ""
(gdb) c
Continuing.
Breakpoint 8, main () at TestStructPoint.c:33
33char *ppstu = pstu->name;
(gdb) p p1
$14 = 0x804a008 "Jimy"
(gdb) p ppstu
$15 = 0x855ca0 ""
(gdb) c
Continuing.
Breakpoint 9, main () at TestStructPoint.c:34
34char *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
37free(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
38free(stu.name);
(gdb) p pstu->name
$20 = 0x804a030 ""
(gdb) c
Continuing.
Breakpoint 12, main () at TestStructPoint.c:39
39free(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
41pstu->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
47return 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)
具体的调试过程说明了其中很多的问题,根据其中的结果可以知道,free结束以后指针变量P(malloc返回)中存储的地址值并没有改变,但是通过该地值已经不能访问之前的分配的存储空间。我采用其他的指针(直接赋值地址以及指针赋值)访问得到的结果也不是正确的值,虽然这不能说明地址中的数据改变了,但说明对释放以后的存储空间再通过其他方法访问不会得到正确的值,但是内存空间中存在值,并不一定是0,因此每一次malloc都清零是必要的。防止野指针也是非常必要的,减少程序错误的概率。
最后说明一下,链表作为结构体的衍生产物,链表的结构体中就有指针变量,因此一定草采用malloc等分配好内存块以后,再对链表进行操作,不然都会导致不同问题的产生。