指针是一把双刃剑,用得好所向披靡,如果用的不好,则会让你的程序出现各种各样的问题,最终导致程序崩溃。
常常有人这么说,C/C++程序员大部分的BUG都是在处理由指针引起的BUG上,比如常见的内存问题、野指针等等。那我们应该如果避免出现指针使用上的问题呢?
不正确的指针声明方式
来看看如下的声明:
int *pH1,pH2;
上面的声明本身没有错误,不过,可能跟我们想的不同,它把pH1声明为整数指针,而把pH2声明为整数。
我们都知道,指针声明和声明其他变量一样,唯一不同的一点就是要在变量前加 *。而上面的星号紧挨着数据类型,而pH1前面加个空格。这样的位置对编译器来说没有区别,但是很容易导致我们看上去pH1和pH2都是整数的指针,但是只要pH1才是整数的指针。
正确的写法如下:
int *pH1,*pH2;
这样,两个变量均声明为整数指针。但是有注意到了吗,没有初始化,这样会导致野指针?
使用指针前必须要初始化
如果在初始化指针之前就使用了指针会导致运行时错误,通常这种指针我们称我为野指针。野指针也叫悬挂指针,是指向“垃圾”内存的指针,使用“野指针”会让程序出现不确定的行为。导致运行时错误。
- 野指针产生的原因
1、 指针变量没有初始化。因此,创建指针变量时,该变量要被置为NULL或者指向合法的内存单元。
2、指针p被free之后,没有置为NULL,让人误以为p是个合法的指针。
3、指针跨越合法范围操作。不要返回指向栈内存的指针或引用
野指针:随机指向一块内存的指针(容易造成内存泄漏) 不一定每次都产生段错误,也许有一次刚好分配到一个已经分配好的空间 。
- 如何避免野指针?
来看看个例子:
#include <stdio.h>
int main()
{
int *ptr;
printf("ptr:%d\n",&ptr);
return 0;
}
编译运行:
这里没有初始化ptr变量,因此它会导致包含垃圾数据,如果ptr中的内存地址超出了应用程序的合法地址空间,这段代码很可能就会执行过程中终止,否则打印出来的就是正好位于那个地址的数据,而且会表示整数。
指针本身并不能告诉我们它是否有效,所以,不能只能靠检查指针的内容来判断它是否有效,不过,有如下方法可以用来处理未初始化的指针。
使用NULL来初始化指针
用assert函数
使用第三方工具
把指针初始化为NULL更容易检查是否使用正确,例如:
int *ptr= malloc(10 * sizeof(int));
if(ptr== NULL) {
// malloc分配内存失败
} else {
// 处理ptr
}
我们还可以使用assert函数来测试指针是否为空值,下列测试了ptr变量是否为空值。如果表达式为真,那么什么都不会发生,如果表达式为假,那么程序会终止。例如:
assert(ptr != NULL);
这样,指针为空的话程序就会终止。函数assert在头文件assert.h上声明。
错误使用解引用
星号在C/C++中还有一个名字就是“解引用”。它的意思就是解释引用,说的通俗一点就是,直接去寻找指针所指的地址里面的内容,此内容可以是任何数据类型,当然也可以是指针。
声明和初始化指针的常用方式如下:
int inum = 10;
int *ptr = &inum;
错误的声明方式:
int inum = 10;
int *ptr;
*ptr = &inum;
注意到最后一行的解引用操作。我们试图把num的地址赋给ptr所指向的内存地址。指针ptr还没有初始化,这里我们误用了解引操作,正确的写法如下:
int inum = 10;
int *ptr;
ptr = &inum;、、// '&'的作用就是把inum变量在内存中的地址给提取出来
&表示的是引用,有&符号,就表示函数内的变量和主函数的变量是同一个,函数内改变它的值,主函数相应的变量也就跟着改变了;没有&符号,就表示函数内的变量是主函数的变量的一个副本,在函数内改变其值,是不会改变主函数中变量的值的。
注意: & 在C/C++中具体 3种语义, 按位与,取地址 和 声明一个引用。
错误使用sizeof操作符
错误使用sizeof操作符的一个例子试图检查错误边界但方法错误,来看看个例子:
#include <stdio.h>
int main()
{
int szbuf[20]= {0};
int *pbuf = szbuf;
printf("sizeof = %d\n",sizeof(szbuf));
for (int i = 0; i < sizeof(szbuf); i++)
{
printf("pbuf:%d\n",*pbuf);
*(pbuf++) = 0;
}
return 0;
}
上面的sizeof(szbuf)表达式返回了80,因此缓冲区长度以字节计是80(20*4)字节元素。上面的for执行了80次而不是20次。最终导致内存访问异常,产生段错误。
可以这样该为:
#include <stdio.h>
int main()
{
int szbuf[20]= {0};
int *pbuf = szbuf;
printf("sizeof = %d\n",sizeof(szbuf)/sizeof(int));
for (int i = 0; i < sizeof(szbuf)/sizeof(int); i++)
{
printf("pbuf:%d\n",*pbuf);
*(pbuf++) = 0;
}
return 0;
}
编译运行:
这个改进就是在for表达式条件中用sizeof(szbuf)/sizeof(int)来编码上面的内存访问异常情况发生。
总结
指针就是一个重要却又充满了麻烦的特性。使用指针的一个理由是在作用域以外使用引用语义。
在C++ 中有智能指针提供了一种模仿指针同时支持边界检查等方法,而C没有智能指针,所有使用指针时候我们需要:
1、当没有指针指向时,要置为NULL
2、给指针指向的空间赋值时,一定要给指针分配空间
3、检查是否分配成功
4、分配成功之后,初始化
5、使用时,注意不要越界
6、使用完之后,free
7、free之后再次置NULL
参考: 深入理解C指针
欢迎关注微信公众号【程序猿编码】,添加本人微信号(17865354792),回复:领取学习资料。或者回复:进入技术交流群。网盘资料有如下: