Node * p = nullptr;
create_list(p);
print_list(p);
create_list创建链表的代码没有问题,print_list打印链表的代码也没有问题。经过跟踪p指针的变化,锁定了范围。其实我想你可能想知道我是如何排查的吧。我就说说排查的过程,给新手调试排查问题参考学习一下。一开始嘛,我也懵逼了。这个代码好像没有问题呀。这个代码是用create_list创建一个链表,将链表的起始地址赋值给p,然后print_list就可以打印p了。不料print_list崩溃了。
我怎么看这个代码,都不觉得有什么问题。简直一个大写的懵逼。一个浅层的代码审核失败。然后就进入深度审核模式,仔细审核每一句代码,然后一遍之后,依然是一个大写的懵逼。
不过此时,凭借多年的专业经验,我感觉到一股无形的杀气。我已经感觉到这里绝对有一个之前不曾见过的陷阱。这个陷阱不是来自基本的语法使用,而是一种思维的陷阱。虽然我能感觉它在这里,但是一下子真的是不知道在哪里。火眼金睛失效了。
没关系。如果第一直觉,火眼金睛都搞不定,那就要动真格了。说明这个问题,不是那么浅显的问题。那么法宝是什么呢?调试!!!
新手不会调试程序,请看这里:《新手应该掌握的调试程序的基本流程》。
我先在create_list(p)里单步调试,一步步查看p指针的变化,发现一切正常。然后再一步步查看print_list(p),函数发现出现内存错误。很明显,提示错误的时候,p指针的值是0。指针为空,必然出现问题。然后就追溯指针,就到了前面这部分代码。对整个代码进行整体的跟踪,在执行第一句时,p的值为0。然后执行了create_list(p)之后,依然是0。按照我们的设想,此时p的值应该是指向被创建的链表的首地址才对。但是这里偏偏就为0。进去create_list(p)查看p的值,却明明有值。
我这让我联想起了数值交换函数swap的案例。代码如下:
void swap(int a,int b)
{
int tmp = a;
a = b;
b = a;
}
这个交换代码,通常用来考察新手对于传参的理解。我们就知道了,下面的方案是可行的:
void swap(int *a,int *b)
{
int tmp = *a;
*a = *b;
*b = *a;
}
变化就是,我们通过了指针实现了数值的交换。所以,我们一般不假思索的会认为,指针确实可以交换数据。而且,传入指针,可以让函数内对指针的操作而修改的数据可以影响到函数外的变量。是的,这个确实没有错。所以,我们上面的代码就看起来一切那么合乎情理,以至于我们发现不了破绽。下面给一个简单的测试代码:
#include <iostream>
using namespace std;
void set(int *p)
{
p = (int*)malloc(sizeof(int));
}
void main(void)
{
int *p = 0;
set(p);//执行后,p的值依然是0
}
我相信大多数人第一眼得到的结果是,好奇怪!!但是问题出现在哪里呢?上面提到了交换数值,就是我们理解的,指针可以解决这个问题。我们可以感觉到,指针p的值应该被改变,为什么不变呢?正是因为不变,所以开头的代码就会崩溃。
在调试走到绝境的时候,不妨从思维的角度来探寻出口。set函数执行后,为什么p的值就要变化呢?
我们需要清醒的认识到,我们在swap用指针交换变量的值,改变的值是被交换的值。也就是说,被改变的值是指针指向的变量的值。但是你发现没有,我们这里改变的不是别人的值,而指针p自身的值。这个和第一个swap交换数值不就一样了。此时指针p传入函数set之后,其实只起到了传值传参作用,因为要改变的是p本身的值,p本身被传递了,不就还是传值。所以,肯定就无法改变外部的p的值。
在修改p本身的值的时候,p就只能看做是一个普通的变量了。此时特殊身份的p也就沦为了一介平民。打个比方,假如一个医生给病人看病,似乎一切都很好使,但是当医生自己病了的时候,自己就没有办法治疗自己了。此时这个医生就丧失了作为医生的能力。需要外力来医治这个医生。此时我们要将这个医生当做一个普通的病人看待。
所以,我们要在函数中修改p的值,就不能直接传递p了,因为传递p和直接传递int是一个含义。我们需要传递指向指针的指针,也就是二级指针了。好像是医治医生的医生啦。这个二级指针就可以改变p的值了。
那么我们就对这个代码进行简单的改造,用二级指针来传值,来修改一级指针的值,类似用一维指针传值来修改普通变量的值。代码如下:
#include <iostream>
using namespace std;
void set(int **p)
{
*p = (int*)malloc(sizeof(int));
}
void main(void)
{
int *p = 0;
set(&p);//此时p就被赋值了set内部分配的内存的地址
}
出现这个错误,相信很多人都会开始一脸懵逼。然后看了上面的解释,又会觉得,原来如此。这里就是一个修改指针值的一个思维陷阱,我们习惯将指针当做一个万能的人,当这个人出现问题时,我们就不会将他看成普通人了。实则在回转想一下,又是多么的合理。