再探“指针”奥秘--换个角度看“指针”!
指针:
指针是现代编程中最容易出错的区域。使用指针式非常复杂的,只有你对所有编译程序内存管理方式有非常深刻的理解之后,才能正确的使用它。
理解指针:
在概念上,每个指针包括两部分:内存存储单元以及对这个存储单元中内容的解释
内存中的存储单元---
内存中的存储单元就是地址,通常是用十六进制数来表示的。指针本身只含有地址,要使用指针指向的数据,你必须到达那个地址并解释那个内存存储单元中的内容。如果你看一下那个存储单元中的存储信息,会发现其中只不过是一些位的组合而已,必须对它加以解释才能使它有意义。
怎样解释存储单元中的内容---
解释存储单元中内容的基础是指针的基础类型。如果指针指向一个整数,它的真实含义就是编译程序把由指针提供的存储单元解释为一个整数。当然,可能会出现整数指针,字符串指针和浮点数指针同时指向同一个存储单元的情况,而此时只有一个指针正确的解释了该单元中的内容。
在考虑指针时,应该想到内存本身并不是具有与之相对应的解释。只有通过使用某一特定类型的指针,才能把某一存储单元中的位解释成有意义的数据。
关于使用指针的几点建议:
对于许多错误来说,找出它是很容易的,但是要改正它则往往比较困难。而指针错误则不然。指针错误主要是由于它指向了错误的地址而引起的。当你给一个错误指针赋值值时,你就想错误的内存存储单元中写进了数据,这叫做内存冲突。有时内存冲突会导致灾难性的后果:有时他会改变程序另一部分的计算结果;有时他会使你的程序意外的跳过某个子程序;而有时它则是什么也没有做。这最后一种情况相当于埋下一颗定时炸弹。当你在想最重要的客户演示程序的前几分钟,他才会突然爆炸,把你的程序搞得一塌糊涂。总之,指针错误的现象看起来似乎是与指针错误无关的。因此,改正指针错误最困难的是找出错误。
需要采取两个步骤来防止指针错误。首先应防止引入指针错误。应采取一切可能的手段来防止引入指针错误,因为它一旦产生便很难改正,其次应尽可能早地发现指针错误。最好是一产生指针编码错误便发现它:
1.把指针操作独立在子程序中:
假设在程序中有几处使用了链表,不如用诸如NextLink()、PreviousLink()、DeleteLink()之类的方法存取子程序来存取他们。通过尽量减少对指针的存取,可以减少犯指针错误的机会,你也就不必费劲心机的去寻找这些错误了。同时由于子程序中的代码相对于其余的部分是独立的,以后也可以方便地重新使用它们。编写分配指针的函数也是一种对数据进行集中控制的方法。
2.在使用指针之前对它进行检查:
在程序的关键部分中使用指针之前,一定要确认它所指向的内存存储单元是合理的。例如,你希望内存存储单元介于StartData和EndData之间,那么应该检查一下StartData之前和EndData之后,看是否把指针指向了这里。同时,你还将决定StartData和EndData中的值是否属于你的环境。而如果你使用存取子程序的话,便可以让这些工作自动进行了。
3.在使用变量之前先检查一下这个变量:
有时需要对指针指向的变量值进行合理性检查,比如,你认为指针将指向一个介于0到1000之间的整数时,应该检查一下长度超过100的字符串。如果你使用存取子程序的话,这一工作也可以自动进行。
4.使用标记字段来查找错误内存:
“标记字段”是仅仅出于查错的目的而加入某一结构的字段。当分配变量时把应该保持不变的值放入标记字段中,当使用这个结构时,检查一下这个标记字段的值。如果标记字段的值与预期不符的话,说明数据有误。
当然,不必每次使用这一变量时都检查一下标记变量。你可以只在丢弃这个变量之前检查一下标记字段的值,如果其值有误则说明在变量生存周期内它的内容有错误,不过,越是频繁第检查标记安全,你也就越容易找到引起错误的原因。
5.使用显示冗余技术:
一个替代标记字段的方法是使用某一字段两次。如果在冗余字段中的数据不匹配,则说明内存有误。如果你直接操作指针的话,这样作需要进行许多内存操作;而如果你能把指针操作孤立在子程序中的话,则只需要在几处添加几个重复代码。
6.释放指针后,把它设为NULL或者NIL:
一种常见的指针错误是“悬挂指针”,即误导了已经被Dispose()或者Free()的指针。指针错误难以发现的原因之一便是有些指针错误并不产生任何现象。在释放指针之后把他们置于NULL,你并没有改变读到了由悬挂指针指向的数据这一事实,但是可以保证在向悬挂指针写数据时产生了错误。与其他许多操作一样,你也可以通过存取子程序来自动进行这项工作。
7.使用额外的指针变量以增加清晰性:
不管怎样,都不要吝啬于供给指针变量。这一原则在别的地方也被说成某一变量应只具有一个公用。这点对于指针而言尤其重要。
编写跟踪指针存储单元的子程序:
通常,对程序危害最大的错误便是使用free()或者display()去再释放已经被释放的指针。不幸的是,几乎没有什么语言能查找这类错误,不过,如果语言不支持我们的话,我们可以自己支持自己。保留一张含有全部已经分配的指针表,将释放一个指针之前检查一下指针是否存在在这个表上:
safe_calloc()---这个子程序与C中的calloc()接受同样的参数。它调用calloc()来分配指针,把新的指针加到以分配指针表上,然后把指针返回到调用子程序中。这样作的另一个好处是你只需要在这一个地方检查有calloc()返回的是否是NULL,从而简化了程序其余部分的错误处理工作。
safe_free()---这个子程序与C中的free()子程序接受同样的参数。它会检查传给他的指针是否已在分配指针表中。如果在的话,safe_free()会调用C中的free()子程序来释放这个指针并将他从表上去掉;如果不存在,safe_free()会打印出诊断信息并未终止程序。
可以很容易对他们做出改动以把对他们的改动转移到别的语言中去。
在calloc()中分配一些冗余字节,并把这些字节作为标记字段。当使用safe_free()释放指针时,检查一下标记字段看是否有错误。在检查之后,抹掉标记字段,以便当你错误地试图再次释放同一指针时可以发现这一错误。
参考资料:《代码大全》