今天来讲一个超9成C语言新手使用指针犯过的错误。
实例:写一个函数让输入指针指向一个另一个地址并对指向的内容进行处理
数据定义:
static T_macList macList = {0}; //全局mac表
T_macList *getMacList(void)
{
return &macList;
}
传递指针并对指针指向的内容做修改:
void getNoflagNode(T_macList *p)
{
T_macList *pmaclist = getmaclist();
T_macList *pNode = NULL;
list_for_each_entry(pNode, &pmaclist->list, list)
{
if(pNode->flag == 0)
{
p = pNode;
return;
}
}
}
使用:
T_macList *node = NULL;
getNoflagNode(node);
问题:
使用 node 时,出现了: segmentation fault(段错误)
实际上node依然是指向了NULL,而且使用的时候也没有处理没有返回导致指针依然是NULL的情况
一个改良:
T_macList *getNoflagNode(void)
{
T_macList *pmaclist = getmaclist();
T_macList *pNode = NULL;
list_for_each_entry(pNode, &pmaclist->list, list)
{
if(pNode->flag == 0)
{
LOG_INFO("p mac is %s \n", pNode->mac);
return pNode;//虽然pNode在栈上申请,但是地址已经被改为了堆上的地址,函数结束,pNode也会在栈上被清理掉,但是传递地址的使命已经完成了
}
}
return NULL;
}
使用:
T_macList *node = NULL;
node = getNoflagNode();
if(node == NULL)
......
使用node时,pNode已经指向了mac表中没有标记类型的节点了
本质分析:
我的同事经常说函数传参的本质是传递一份拷贝;
我的理解是对待指针参数是使用一个新的临时指针去指向参数,对待非指针变量只是新建一个值相同的变量来供内部使用
所以可以这么理解:
所以在一开始直接使用双重指针就没事了
void getNoflagNode(T_macList **p)
{
T_macList *pmaclist = getmaclist();
T_macList *pNode = NULL;
list_for_each_entry(pNode, &pmaclist->list, list)
{
if(pNode->flag == 0)
{
*p = pNode;
return;
}
}
}
使用:
T_macList *node = NULL;
getNoflagNode(&node); //注意传递的是node的地址
if(node == NULL)
......
如果还不够生动,或者虚拟变量阻碍了理解,那么再来看2个简单的实例:
实例1:修改字符串内容
void change( char *p )
{
p = "bbb";
printf("%s\n",p);
}
int main()
{
char v[4] = "aaa";
printf("v1=%s\n",v);
change(v);
printf("v2=%s\n",v);
}
//结果:
//v1=aaa, p=bbb, v2=aaa
change函数根本不起作用,很多新人会以为 p = “bbb” 是给字符指针变量赋值,实际上是给 p 的地址赋值,赋予了字符串 “bbb” 的地址。
解决方法:
同一开始的案例,使用双重指针可以解决。
void change( char **p )
{
*p = "bbb";
printf("%s\n",*p);
}
int main()
{
char v[] = "aaa";
printf("v1=%s\n",v);
change(&v);
printf("v2=%s\n",v);
}
//结果:
//v1=aaa, p=bbb, v2=bbb
实例2:修改数字指针指向的内容
void change( int *p )
{
int i = 20;
p = &i;
}
int main()
{
int i = 10;
int *a = &i;
change( a );
}
这段代码和实例1的第一段代码本质上是一样的,但是这段代码大家都可以看出问题;实例1的第一段代码之所以很多人没有看出问题,是因为把字符串当做了普通的字符变量,而忽略了它的一个本质,传递时是字符串的地址。