C/C++知识点总结(三)指针问题

一,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

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值