上周在解决一道课后习题的时候,偶然间发现了一个自己从未注意过的问题,问题描述如下:
在遍历一个循环链表时,我发现在我调用free()函数删除了一个节点之后,仍然能用printf打印出原先的数据,起初我以为是巧合,并未加以注意。今天我又尝试free其他节点,之后仍然能够通过printf打印出原先的值,这个问题立刻引起了我的注意。下面将出现问题的代码附在下方,供各位查看:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int status;
typedef int elemtype;
typedef struct node{
elemtype data;
struct node * next;
}node;
typedef struct node * linkqueue;
int main(void){
linkqueue rear,pmove,head,qmove;
int i=0;
pmove=rear=(linkqueue)malloc(sizeof(node));
if(!pmove||!rear)
return ERROR;
head=pmove;
/*建立循环链表,并让0~9入队*/
for(i=0;i<10;i++){
qmove=(linkqueue)malloc(sizeof(node));
qmove->data=i;
rear->next=qmove;
rear=qmove;
}
rear->next=head;
pmove=head->next;
printf("将0~9入队后\n");
while(pmove!=head){
printf("%d ",pmove->data);
pmove=pmove->next;
}
printf("\n");
/*出队算法*/
pmove=head->next;
printf("出队元素为:%d\n",pmove->data);
linkqueue smove=pmove->next;
free(pmove);
***printf("%d ",pmove->data);/*注意这行代码,这是问题之所在*/***
pmove=smove;
printf("出队后,遍历队列\n");
while(pmove!=head){
printf("%d ",pmove->data);
pmove=pmove->next;
}
printf("\n");
}
从理论上看,free之后不应该打印出原数值,但事实是,我加粗的那行代码成功地打印出了原数值,在我google之后,得到了这样的解答:
“你在free(p)之后,最好加上p = NULL;
要不然容易导致野指针。你在free(p)之后,你只是使用
if(p != NULL)
你想的是用来进行防止误用操作对吧。。
你进入了一个误区,误认为free(p)之后,p就指向了NULL,而其实不然。
free(p)的言外之意就是告诉编译器:大家注意啦哈,这块内存我现在不用了,你们谁想用就拿去用哈。而p在这里你可以完全理解成就是这块内存的地址,也就是告诉编译器,这块内存现在不被占用了,而里面的内容此时就是我们所说的“垃圾”,因为作为主人的我已经丢弃它了,里面的内容就是不可控的。
注意p是个地址,你没有强行置为空,那还是原来的那个值。只是里面的内容不受控啦,有可能不会变,有可能会被改写,而结果是未知的。”
之前在知乎上总是看到大牛们大书特书C语言内存管理机制的缺陷,野指针的种种危害。却从未想过究竟什么是“野指针”,今天遇到的这个问题就是野指针带来的,看来自己对C指针的认识还不够深刻,还需要更深层次的学习,特地写下此文,加以反思。
看来水平离熟练使用C还差很远,静下心继续反思吧。
2016-04-27 晨
于教学实验综合楼