1.指针的初始化
指针变量的零值是“空”(记为NULL)。在<stdio.h>中#define NULL 0,尽管NULL 的值与0 相同,但是两者意义不同。假设指针变量的名字为p,它与零值比较的标准if 语句如下:
if (p == NULL) // p 与NULL 显式比较,强调p 是指针变量。
当我们试图析取(dereference)一个空指针NULL时,例如int *p = NULL;当我们试图cout<<*p;析取p时,将会出现内存读错误。因为0x00000000为进程私有地址,是不允许访问的,因此将会弹出应用程序错误:“0x********”指令引用的“0x00000000”内存。该内存不能“read”。
如果定义指针时把它初始化为NULL,我们的代码就能用if(ptr==NULL)来判断它是不是有效的指针。
因此,建议定义指针后将其初始化为NULL或指向合法内存。
典型错误:char *dest; char *src = “Fantasy”; strcpy(dest, src);
2.检查一个指针是否有效
malloc returns a void pointer to the allocated space, or NULL if there is insufficient memory available.
If there is insufficient memory for the allocation request, by default operator new returns NULL.
用malloc 或new 申请内存之后,应该用if(p==NULL)检查指针值是否为NULL,防止使用指针值为NULL 的内存。如果指针p 是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。
3.野指针
1)、指针变量在定义后如果没有初始化是野指针,其值不为NULL,指向一个随机地址。故在使用*析取(dereference)之前,应确保指针指向合法的地址。
2)、delete某个指针后,指针所指向的变量(对象)被释放(生命周期结束),但是该指针变为野指针。
4.delete干掉了什么
一般用new运算符动态分配出来的堆内存,需要我们配对调用delete来显式回收内存,以防内存泄漏。但delete只是把指针所指的内存给释放掉,并没有把指针本身干掉。下面的测试中发现指针pn3被delete以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,pn3成了“野指针”。如果此时不把pn3设置为NULL,会让人误以为pn3是个合法的指针。
如果程序比较长,我们有时记不住pn3所指的内存是否已经被释放,在继续使用pn3之前,通常会用语句if (pn3!= NULL)进行防错处理。很遗憾,此时if 语句起不到防错作用,因为即便pn3不是NULL 指针,它也不指向合法的内存块。因此,建议在delete释放了内存之后,应立即将指针赋值为NULL,防止产生“野指针”。
另外,在实际程序中,可能不止在一个模块中操作new申请的内存块,我们可能会将new返回的指针作为参数传递给另一个函数模块处理,以享指针直接操作内存之快捷。但是一定要把握这个指针的生命周期,看哪里不用了就适时delete结束其生命。
通常我们会在析构函数中使用delete,在本对象被析构之前先把对象里使用new实例化的一些对象析构,防止内存泻漏。如果在程序中显式调用delete pClass来清除对象,则delete会去调用被pClass所指向对象的析构函数来释放内存。编译器对普通自动对象将调用其析构函数,但是对于new出来的pClass,则必须显式调用delete将其删除。
5.测试代码
//testPointer.cpp
#include <iostream>
using namespace std;
int main()
{
int *pn1;
cout << “代码:int *pn1;” << endl;
cout << “&pn1 = ” << &pn1 << endl;
cout << “pn1 = ” << pn1;
// pn1不知所指,可能乱指一气,故下面一句可能有输出
// cout << “*pn1 = ” << *pn1 << endl << endl;
pn1 = NULL;
cout << “代码:pn1 = NULL;” << endl;
cout << “pn1 = ” << pn1 << endl;
// 若cout << *pn1 试图访问内存0x00000000(NULL)将抛出异常
// 因为内存0x00000000已经被用户进程使用,处于私有保护状态,不能读
int n1 = 5;
pn1 = &n1; // 指针pn1指向n1
cout << “代码:int n1 = ” << n1 << “;” << “pn1 = &n1;” << endl;
cout << “&pn1 = ” << &pn1 << endl;
cout << “pn1 = ” << pn1 << endl;
cout << “*pn1 = “ << *pn1 << endl ;//
int n2 = 10;
pn1 = &n2; // 指针pn1另有所指
cout << “代码:int n2 = ” << n2 << “;” << “pn1 = &n2;” << endl;
cout << “&pn1 = ” << &pn1 << endl;
cout << “pn1 = ” << pn1 << endl;
cout << “*pn1 = “ << *pn1 << endl;//
// 不能调用delete pn1;因为pn1已经指向了栈里分配的n2
int *pn2 = new int; // int *pn2这个指针变量4 byte是在栈里分配的,
// new int这个4 byte是堆里分配的
cout << “代码:int *pn2 = new int;” << endl;
cout << “&pn2 = ” << &pn2 << endl;
cout << “pn2 = ” << pn2 << endl;
// 内存分配虽然成功,但是尚未初始化就引用它,以下输出为随机数或记忆数
cout << “*pn2 = “ << *pn2 << endl ;
pn2 = &n2; // 指针pn2指向n2
cout << “代码:int n2 = ” << n2 << “;” << “pn2 = &n2;” << endl;
cout << “&pn2 = ” << &pn2 << endl;
cout << “pn2 = ” << pn2 << endl;
cout << “*pn2 = “ << *pn2 << endl;
// 不能调用delete pn2;因为此时pn2已经指向栈里分配的n2
// 所以堆上分配的那4 byte内存泄漏了!
int *pn3 = new int; //
*pn3 = 10;
cout << “代码:int *pn3 = new int;*pn3 = ” << *pn3 << endl;
cout << “&pn3 = ” << &pn3 << endl;
cout << “pn3 = ” << pn3 << endl;
cout << “*pn3 = “ << *pn3 << endl;
delete pn3; //
cout << “代码:delete pn3;” << endl;
cout << “&pn3 = ” << &pn3 << endl;
cout << “pn3 = ” << pn3 << endl; // 释放了内存却继续使用它
cout << “*pn3 = “ << *pn3 << endl; //
pn3 = NULL; //
cout << “代码:pn3 = NULL;” << endl;
cout << pn3 << endl;
return0;
}
运行结果:
代码:int *pn1;
&pn1 = 0012FF7C //整形指针变量pn1(栈向低地址增长)
pn1 = CCCCCCCC
代码:pn1 = NULL;
pn1 = 00000000
代码:int n1 = 5;pn1 = &n1;
pn1 = 0012FF78 //整形变量n1
*pn1 = 5
代码:int n2 = 10;pn1 = &n2;
pn1 = 0012FF74//整形变量n2
*pn1 = 10
代码:int *pn2 = new int;
&pn2 = 0012FF70//整形指针变量pn2
pn2 = 003724E8//(堆向高地址增长)
*pn2 = -842150451
代码:int n2 = 10;pn2 = &n2;
pn2 = 0012FF74
*pn2 = 10
代码:int *pn3 = new int;*pn3 = 10
&pn3 = 0012FF6C//整形指针变量pn3
pn3 = 00372520
*pn3 = 10
代码:delete pn3;
pn3 = 00372520
*pn3 = -572662307
代码:pn3 = NULL;
00000000
参考:
《高质量C++编程指南》 林锐
《C++ Primer》 Stanley B. Lippman
《野指针》
《C语言野指针》