一,C++内存泄漏、野指针和空指针
(1)避免内存泄露
在C/C++中,通过动态内存分配函数(如malloc系统函数)或者new运算符分配的动态内存在使用完之后需要手动释放。否则会造成内存泄露。
即使在malloc/new后显示调用了free/delete释放内存,但是由于异常可能会导致释放内存的free/delete语句得不到执行,也会发生内存泄露。
(2)不要使用野指针
野指针也叫悬挂指针,是指向“垃圾”内存的指针,使用“野指针”会让程序出现不确定的行为。注意,野指针不是NULL指针, 它比NULL指针更容易犯错,因为它不能通过形如 if (NULL == p)的判断语句来预防,只能我们自己在写代码时多注意。
- 指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
- 指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。
#include<stdio.h>
#include<stdlib.h>
int main()
{
char *p=(char *)malloc(sizeof(char)*100);
strcpy(p, “hello”);
printf(“%s ”,p);
free(p); // p 所指的内存被释放,但是p所指的地址仍然不变
if(p != NULL) // 没有起到防错作用
strcpy(p, “world”); // 出错
printf(“%s \n”,p);
}
free()释放的是指针指向的内存!注意!释放的是内存,不是指针!这点非常非常重要!指针是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在!只不过现在指针指向的内容的垃圾,是未定义的,所以说是垃圾。因此,前面我已经说过了,释放内存后把指针指向NULL,防止指针在后面不小心又被解引用了。
- 不要在函数中返回局部变量的地址,如果代码的逻辑非要是一个局部变量的地址,那么该局部变量一定要申明为static类型,因为static变量的生存期是整个程序运行期间。
c/c++中,局部变量是存放在栈中的,它的特点是随函数调用时创建随函数结束时销毁,因此在程序中将局部变量的地址返回后赋值给一个指针,这个指针指向的是一个已经被回收的内存,这也是一种野指针。
看看下面的例子,原本是想将fun函数中的变量i的地址返回给p,用p访问这个变量,这个打印出*p是32767,并不是变量i的值8。像这种bug,一旦在大的项目中出现是很难定位的。
class A
{
public:
void Func(void)
{
cout << “Func of class A” << endl;
}
};
class B
{
public:
A *p;
void Test(void)
{
A a;
p = &a; // 注意 a 的生命期 ,只在这个函数Test中,而不是整个class B
}
void Test1()
{
p->Func(); // p 是“野指针”
}
};
函数 Test1 在执行语句 p->Func()时,p 的值还是 a 的地址,对象 a 的内容已经被清除,所以 p 就成了“野指针” 。
野指针的危害:
野指针的问题在于,指针指向的内存已经无效了,而指针没有被置空,解引用一个非空的无效指针是一个未被定义的行为,也就是说不一定导致段错误,野指针很难定位到是哪里出现的问题,在哪里这个指针就失效了,不好查找出错的原因。所以调试起来会很麻烦,有时候会需要很长的时间。
野指针的避免-正确使用指针
野指针出现了就是程序有问题,它在程序里是不能做任何判定的,所以只能避免
通常避免野指针的办法是正确的使用指针
- 声明一个pointer的时候注意初始化为null
int* pInt = NULL;
- 分配完内存以后注意ASSERT
pInt = new int[num];
ASSERT(pInt != NULL);
- 删除时候注意用对操作符
对于new int类型的,用delete
对于new int[]类型的,用delete []
- 删除完毕以后记得给他null地址
delete [] pInt;
pInt = NULL;
- 记住,谁分配的谁回收,不要再一个函数里面分配local pointer,送到另外一个函数去delete
- 返回local address是非常危险的,如必须这样做,请写注释到程序里面,免得忘记
(3)不要使用NULL指针
大家都知道,在程序中不能使用NULL指针,但是如果不注意,程序中还是有可能在你的意料之外就使用到NULL指针,下面看两个比较容易出问题的例子。
动态内存分配函数分配内存的时,有可能会分配失败,此时返回NULL
从程序运行结果来看,malloc分配失败返回NULL赋给p,再通过p访问其所指向的0地址内存内容时,出现"Segmentation fault"错误。
在使用内存分配函数分配内存的时候,应该用i f(p==NULL) 或if(p!=NULL)进行防错处理。
此外,在含有指针参数的函数,也是有可能会误用到NULL指针,当调用该函数时传递的指针是个空指针,如果没有if(p!=NULL) 的判断条件,那么在后面使用指针的时候麻烦就大了,下面的例子就是这种情况。
对于含有指针参数的函数,也应当在函数入口处用if(p==NULL) 或if(p!=NULL)进行防错处理。
二、使用指针做函数返回值
(1)使用栈内存返回指针是明显错误的,因为栈内存将在调用结束后自动释放,从而主函数使用该地址空间将很危险。
char* GetMemory()
{
char p[] = "hi";
return p;
}
void main()
{
char *str = GetMemory(); //出错! 得到一块已释放的内存
printf(str);
}
(2)使用堆内存返回指针是正确的,但是注意可能产生内存泄露问题,在使用完毕后主函数中释放该段内存。
char* GetMemory()
{
char *p = new char[100];
return p;
}
void main()
{
char *str = GetMemory();
delete [] str; //防止内存泄露!
}
三、使用指针做函数参数:
有的情况下我们可能需要需要在调用函数中分配内存,而在主函数中使用,而针对的指针此时为函数的参数。此时应注意形参与实参的问题,因为在C语言中,形参只是继承了实参的值,是另外一个量(ps:返回值也是同理,传递了一个地址值(指针)或实数值),形参的改变并不能引起实参的改变。
(1)直接使用形参分配内存的方式显然是错误的,因为实参的值并不会改变,如下则实参一直为NULL
void GetMemory(char* p)
{
char *p = new char[100];
}
void main()
{
char *str;
GetMemory(str);
strcpy(str, "hi"); //出错! str = NULL!
}
(2)由于通过指针是可以传值的,因为此时该指针的地址是在主函数中申请的栈内存,我们通过指针对该栈内存进行操作,从而改变了实参的值。
void Change(char *p)
{
*p = 'b';
}
void main()
{
char a = 'a';
char* p = &a;
Change(p);
printf("%c"n", a); //值a改变!
}
(3)根据上述的启发,我们也可以采用指向指针的指针来进行在调用函数中申请,在主函数中应用。如下:假设a的地址为0x23,内容为'a';而str的地址是0x46,内容为0x23;而pstr的地址是0x79,内容为0x46。
pstr(0x79)--> str(0x46)-->&a(0x23)
我们通过调用函数GetMemory