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

原创 2013年12月05日 14:47:49
相关笔记: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。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

C语言指针未初始化情况

#include #include #include typedef struct node Node,*P_Node; struct node { int data; ...

指针的初始化和赋值

1、指针的初始化 初看起来,指针的初始化和赋值好像很混乱,又是*,又是&,时不时又出来一个数组。其实总结起来很简单: int *p; int a=25; int b[10]; int *m=...

指针的使用注意事项(个人体…

原文地址:指针的使用注意事项(个人体会)作者:残翅天使的眼泪1.指针一定要有指向(赋值),就是一定要“初始化”。否则容易产生莫名的错误。 初始化又分为3种。 第一种,指向实心砖头(NULL)。即:p...

【转载】C语言中指针使用的注意事项

相信大家对指针的用法已经很熟了,这里也不多说些定义性的东西了,只说一下指针使用中的注意事项吧。 一.在定义指针的时候注意连续声明多个指针时容易犯的错误,例如int * a,b;这种声明是声明了...

指针参数--指针做参数时应该注意的地方

程序1: void myMalloc(char *s) //我想在函数中分配内存,再返回 {   s=(char *) malloc(100); } void main() {   char *p=N...

C指针的值和地址 [李园7舍_404]

在C语言中,函数传参分为两种:传址和传值。 传值是一个复制的过程。 传址对于变量和对象来说是传址,而针对于同一级的指针来说也是传值。传址针对的对象是指针指向的对象,此时对指针的一切行为都是...

C指针和堆空间 [李园7舍_404]

相关笔记:C指针和堆空间、C malloc()实际分配空间大小。 0 堆内存的在计算机内存中的形式 根据《The C Programming language》推测得到堆内存,图中的Hea...

C自定义函数的可变参数列表实现 Windows APIS目录遍历程序 [李园7舍_404]

The  C Programming Language例子笔记一。 The C Programming Language例子笔记二(本来应包含解析声明部分)见堆及堆分配。 1可变参数列表函数实现...

C51 延迟函数编写 [李园7舍_404]

背景:STC89C52   1_nop_() 功能:_nop_函数用于在代码中产生一条NOP指令。这条指令能被用来延迟一个机器周期的时间。 返回值:无。 头文件:#include   ...

51 串行通信知识点整理 [李园7舍_404]

串行口编程首先与之联系紧密的是各种寄存器了。然后再整理各种串口通信模式下的通信方式。然后再将波特率的计算公式贴上。至于MAX232、TMOD、TCON等寄存器应该是另一块知识点。TMOD、TCON之前...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用C指针的几个基本注意点 [李园7舍_404]
举报原因:
原因补充:

(最多只允许输入30个字)