【原创】指针和下标的10条对比


 

摘要:在编程语言中,指针和下标都是访问数据的有效手段,本文以C/C++语言为例,讲解它们之间的区别。

 

要想完全理解本文,读者可能需要:
1.
至少熟悉一门含有指针和下标语义的编程语言

 

指针的值,是一个内存地址,人不容易识别其含义,也很难比较2个指针之间的关系。

下标的值,是一个从小往大的小范围的整数,人比较容易识别,2个下标之间的关系也一目了然。

 

效率

通过指针访问数据,一般是一次间址访问。

通过下标访问数据,需要先和基地址一起计算得到真实地址,然后再进行访问,应该会比指针访问要慢,但差异是微乎其微的,而且还受到编译器优化的影响,所以2者一般可以认为速度都很快。

 

约束

使用下标,前提是需要知道具体类型的基地址,而使用指针则没有这个要求。

使用下标还有个隐含的约束,那就是要事先申请到一块连续的内存空间,而指针则没有这样的要求,但我不觉得这个约束是下标的缺点,虽然使用指针比较方便,但由于使用下标必须是存在一块连续的内存空间,这导致了使用下标会有更好的性能表现。为什么?呵呵,其实这也是只是从概率层面来讲的,操作系统里有个概念叫“缺页”(不知道的请Google),从概率上来讲,连续的内存发生缺页的可能性比离散的内存的发生缺页的可能性会低很多,一旦发生缺页,性能就会带来损失;而且,因为是连续的空间,所以它也容易受益于计算机内部的各种各样的缓存。遍历相同规模的数组比遍历相同规模的链表的速度要快(他们的时间复杂度一样,都是O(n)),便是这一论断的一个例证。

 

复杂性

这是一个实实在在的使用下标的缺点。当你决定用下标代替指针前,请仔细考量这种改变所带来的复杂性及增加代码读者的理解难度。

 

通用性

支持下标的编程语言比支持指针的编程语言数量要多。

 

判断合法性

指针,你很难确定一个指针指向的数据是否合法,大部分开发者只是把指针和NULLnil\空)进行判断,显然这无法判断所有的异常指针。

下标则稍微好点,一般可以知道下标的上下限(C/C++语言 0 数组长度 -1),判断合法性比较容易。

 

功能

指针能做的,下标一般也能做,比如可以用数组 + 下标 实现一个链表,这种链表一般被称为“静态链表”,关于“静态链表”的详细论述请Google

 

一致性

同一程序被启动多次,程序里指针的值可能每次都不一样,而下标的值应该是一样的(假设是单线程环境或多线程同步环境)。

2个程序跨机器通信,传输指针的值,一般是无用的,因为指针的值是和平台环境相关,传输指针的值,有点像“刻舟求剑”。

2个程序跨机器通信,传输下标的值,虽然下标的值跨机器之后仍然是有用的,但考虑到下标本质上是个整数,所以需要考虑字节序的问题,什么是字节序?请Google

 

可用性

当在表示数组元素时,下标和指针有个很微妙的不同,当数组调用realloc之类的扩展内存的操作后,指针很可能已经失效了,而下标依然是可以使用的。其中很经典的一个例子是用迭代器遍历STL中的vector,然后在遍历过程中删除某些元素。有兴趣的读者可以动手试试。

 

移植性

一个指针本身占用的内存大小一般和CPU/操作系统等环境相关,程序员很难控制。比如sizeof(void *)的大小,在32位计算环境下为4,而在64位计算环境下为8。所以假设一个指针为4个字节,当程序在移植的时候,可能会带来麻烦。当在64位计算环境下,每个指针的本身的大小是32位环境下的2倍,这会造成计算机内存的额外消耗。其实,我们可以发现,下标是一种简单有效的指针压缩技术,基址 + 偏移量(下标)=真实地址,因为下标是数字,所以程序员可以自由选择8位整数,16位整数,32位整数,64位整数,比较灵活,同时也会减少内存占用。题外话,在Java 6里面,就有针对64JVM环境下的指针压缩技术,看来64位指针的内存消耗还是需要引起一定的重视。当然,这也和Java语言的特点有关,因为Java语言里绝大多数的对象都是定义在heap里的,而不像C++,可以自由控制对象定义在stack上还是heap上。对象放在heap里,那么指针就是少不了的,而且一个对象内部可能还包含其他对象的指针,据说64JVM的内存消耗是32JVM内存消耗的1.5倍。

 

小结:对不同的事物下一个通用的结论总是很难的,本文也不例外,指针和下标各有优缺点,读者应该根据使用场景进行合理选择。