深入探索 C/C++ 数组与指针的奥秘之二:数组名是一个指针常量吗?
数组名是一个指针常量这种观点来源于数组名在表达式计算中与指针的结果等效性。例如下面的代码:
int a[10], *p = a, *q; q = a + 1; q = p + 1;
在效果上看,a + 1 与 p + 1 是相同的,这很容易给人一种 a 就是 p 的假象,但,这仅仅是假象。鉴于指针常量包含了指针和常量两类概念,我们可以把这个问题分开两部分进行讨论。
一、数组名是指针吗?
在《C 与指针》一书中,作者用一个著名的例子阐述了数组名与指针的不同。在一个文件中定义:int a[10];然后在另一个文件中声明:extern int *a; 笔者不在这里重复其中的原理,书中的作者试图从底层操作上阐述数组名与指针的不同点,但笔者认为这个例子存在一些不足,a 在表达式中会转换为一个非对象的符号地址,而指针 a 却是一个对象,用一个非对象去跟一个对象比较,有“偷跑”的嫌疑,这个例子只是说明了数组名的非对象性质,只能证明对象与非对象实体在底层操作上的不同,事实上,如上一章所述,指针也有非对象形态。笔者认为,无须从底层的角度上花费那么多唇舌,仅仅从字面上的语义就可以推翻数组名是一个指针的观点。
首先,在 C/C++ 中,数组类型跟指针类型是两种不同的派生类型,数组名跟指针是两种不同类型的实体,把数组类型的实体说成“是”另一个类型的实体,本身就是荒谬的;
其次,a + 1 在效果上之所以等同于 p + 1,是因为 a 进行了数组到指针的隐式转换,这是一个转换的过程,是 converted to 而不是 is a 的过程。如果是两个相同的事物,又怎会有转换的过程呢?当把 a 放在 a + 1 表达式中时,a 已经从一个数组名转换为一个指针,a 是作为指针而不是数组名参与运算的;
第三,a + 1 与 p + 1 是等效关系,不是等价关系。等价是相同事物的不同表现形式,而等效是不同事物的相同效果。把数组名说成是指针实际上把等效关系误解为等价关系。
因此,数组名不是指针,永远也不是,但在一定条件下,数组名可以转换为指针。
二、数组名是一个常量吗?
看见这句话有人会觉得奇怪,数组定义之后就不能改变了,数组名不就是个常量吗?在表达式中,数组名的确可以转换为一个不变的符号地址,但在C 中,不变的实体不一定是常量!而且,C/C++ 有常量与常量表达式之分,常量与常量表达式是两种不同的实体,但常量表达式可以作为常量使用。C/C++ 中的常量虽然有所不同,但都不包括数组或数组名,而且数组名也不一定是常量表达式。
请在 C90 的编译器中编译如下代码,注意不能是 C99 和 C++ 的,因为 C99 和 C++ 不再规定数组的初始化器必须是常量表达式,会看不到效果:
int main( void ) { static int a[10], b[10]; int c[10], d[10]; int* e[] = { a, b }; /* A */ int* f[] = { c, d }; /* B */ return 0; }
B 为什么不能通过编译?是由于自动数组名并不是常量表达式。在 C 中,常量表达式必须是编译期的,只在运行期不变的实体不是常量表达式,请看标准的摘录:
6.6 Constant expressions
A constant expression can be evaluated during translation rather than runtime, and accordingly may be used in any place that a constant may be.
c 和 d 是自动数组,首地址在编译期是不可知的,因为这样的对象在编译期还不存在;a 和 b 是静态数组,静态对象从程序开始时就已存在,因此 a 和 b 的首地址在编译期是已知的,它们都属于常量表达式中的地址常量表达式。
所以,C/C++ 中的数组名,都不是常量。C 中的数组名,是否常量表达式要视其存储连续性而定,全局数组、静态数组名都是常量表达式,而自动数组名不是。在 C++ 中,由于不再规定常量表达式必须是编译期的,因此 C++ 的数组名都是常量表达式。
原文链接:http://blog.csdn.net/supermegaboy/archive/2009/11/23/4855018.aspx。