使用C指针的几个基本注意点 [李园7舍_404]

来源:互联网 发布:淘宝业务员提成怎么算 编辑:程序博客网 时间:2024/04/30 00:22
相关笔记:C指针和堆空间、C malloc()实际分配空间大小。

C中,使用动态分配的形式使用堆内存空间。涉及“动态内存分配函数(malloc)”,“动态内存释放函数(free)”。初学包含指针的程序设计语言,对堆空间的使用会出现很多的问题似乎是不可避免的过程。总结一下关于动态分配的笔记,关于内存管理此篇笔记处于初级水平,还未到深入地步。

C程序中实现动态分配离不开指针。动态内存分配涉及“指针的定义”,“使用malloc函数”,“使用分配成功的堆空间”,“释放堆空间”。每一步都可能会程序带来潜在的错误。


1 未初始化的指针

对于C语言程序来说,系统会为初始化的全局变量赋予初值0。未初始化变量的值不定。在MinGW(gcc for windows)验证如下:

#include <stdio.h>#include <stdlib.h>int *pG1, *pG2;int main(){    char *pL1, *pL2;    printf("pG1: %p, pG2: %p; pL1: %p, pL2:%p\n", pG1, pG2, pL1, pL2);    return 0;}


编译后的警告石两个局部变量指针未初始化,执行结果如下:

未初始化指针值执行结果

  • 未初始化全局变量指针的值为0。未初始化局部指针变量的值不定。
  • 指针值所代表的内存中内容不定。(如果在以上程序中输出pL1pL2中的内容,则会导致程序出现异常)。


未初始化的指针变量就成为了野指针(指针的随机值代表的内存不可用)。


2 malloc()内存

可先参见位经malloc()函数申请分配的堆内存在计算机中的形式:计算机中的堆。


经malloc()分配过得堆内存结构如下:

Read FromThe C Programming Language》。


可用的堆内存块以“可用堆内存链表”的形式存在。malloc()进行动态分配的特点:

  • malloc()根据用户所需分配内存的大小n (bytes)在“堆链表”(见未使用过得堆内存)里搜索。直到搜索到一个大于等于n字节的堆内存块为止。如果此堆内存块的大小刚好为n,则直接将首地址返回给用户;如果此内存块的大小大于n,则将此块堆内存分裂,将大于n部分的堆内存留在可用堆内存中,以“堆链表”的形式和其它未分配的堆内存发生联系。
  • 如果整个堆链表所代表的堆内存块都没有大于等于n的堆内存块,系统将给“堆链表”链接一个更大的区域供其使用。要是这一步也失败了,malloc()函数就返回NULL给用户。


malloc()函数分配内存成功则返回可用堆内存块的首地址,若分配失败则返回空。在使用malloc()后一定要判断堆内存是否成功。若对内存分配未成功使用指针操作内存也会使程序出现异常。动态分配内存时要采取以下结构:

char *pL =NULL;……pL       = (char *)malloc( sizeof(char) * size);if(pL){      …}

分配成功后,得到的堆内存首地址一定要保存,不然后来无法释放堆内存而造成内存泄露。而且不可使用未初始化的pL指向的内存块。



3 free()内存

当使用free()函数释放堆内存的时候,free()函数将堆内存插入到于要释放堆内存地址最邻近的一个位置上,尽可能的使堆内存以大块的形式存在而不至于让堆内存称为碎片。


释放未指向任何堆内存块的指针也会造成内存泄露。所以在释放指针前的一个基本操作是判断指针内容是否为空,free(p)后只是将p指向的内存回收,p的值依旧存在,为避免再次使用p的值还需要将p赋值为NULL(因为使用指针前都会判断是否为NULL)。释放堆内存块采取这样的程序结构:

if(pL){      free(pL);      pL      = NULL;}


4指针赋值为NULL的道理

有笔记“C中的void和NULL”表面引用NULL指针的后果。为了更好的利用指针,避免野指针(指针所指的内存块不可用)的使用在所有使用指向堆内存块的指针前都采取如此的结构:

if(p){      //通过指针操作堆内存……}

定义指针后将其值赋值为NULL。此时指针指向的内存地址为NULLNULL对指针的赋值是将指针置成空指针(什么也没有指向)还是将指针指向了一段特殊的地址取决于编译器,编程中我们不需要了解NULL到底代表什么,只需要用NULL来避免指针带来的后果。

定义指针后将其赋值为NULL之后的好处在于避免系统给予局部指针变量的随机值,我们在使用指针前(malloc()除外)都判断一下指针的值是否为NULL,只有在不为空的情况下才能对此进行操作,如free(p),若在不判断p是否为空的情况下进行free(p)操作则会造成内存泄露。


完全使用完某个指针或释放指向堆内存的指针后,将其值赋值为空。指向堆内存的指针在释放完需要赋值为空的理由见free ()堆内存。对于指针定义时初始化和完全使用完指针后再将其值赋为NULL的道理在于所有使用指针的语句前都会有有判断指针是否为NULL的语句。尤其是在子函数内判断指向堆内存块的指针实参是否为NULL



5在含指针参数的函数内使用断言

(1)用断言判断指针是否为NULL

判断指针是否为NULL的主要针对对象是指向堆内存的指针。比如在以下内存拷贝函数中:

flag  my_strcpy(char  *StrTo,  char  *StrFrom){      if(!StrTo || !StrFrom)      {            return  -1;      }      char  *StrToL, *StrFromL;      StrToL      = StrTo;      StrFromL    = StrFrom;      while(*StrToL++ = * StrFromL++)            NULL;      return 0;}

程序中首先判断两个地址是否为空。判断StrTo是为了了解StrTo是否指向一段空间。当然若StrTo指向一个常数,往后拷贝操作还得出错。


像这样带指针参数的子函数内都很有必要有这么一段判断指针是否为NULL的语句,故而可以将这样的代码写成函数来供大家使用,再考虑此代码段比较小可以用宏代替。这样的(带参数)宏可称为断言,因为当指针未空时就退出子函数(如assert())。


如以上一段判断子函数是否为空可以用如下宏代替,形成一个断言:

#define  MY_ASSERT(pStrTo, pStrFrom)     if(!StrTo || !StrFrom)   \ {                   \                                              return  -1;    \ }

然后在每个程序中直接调用MY_ASSERT(pStrTo, pStrFrom);即可。由于这样的宏(断言)可能供许多函数的使用,所以一定要保证它的正确性。


(2)内存块重叠

内存块重叠指多个指针指向的内存有重叠的情况。对内存块的操作是否会影响源内存块的内容(如内存数据拷贝)。

两指针指向的内存块重叠

如上图将p2指向内存的数据拷贝给p1代表的内存中去后,p2指向的内存块数据也被改变。堆内存块的操作不要有副作用。


6总结

(1)NULL(因其特殊性)来统一标识指针的可用性。使用指针前都应该判断一下指针是否为NULL

(2)将局部指针变量初始化为NULL(消除系统给其赋的随机值,系统为其赋随机值也就造就了野指针)。

(3)指针用于指向堆内存时需要注意:

  • malloc()后一定要判断是否malloc()成功。malloc()成功后一定要保存所分配堆内存块的首地址。

  • 使用堆内存块前要初始化。

  • 使用堆内存块不可越界。

  • 正确释放每个堆内存块。且释放后将指针的值重新赋值为NULL

(4)使用指针前都应该判断一下指针是否为NULL


Note Over。

原创粉丝点击