C/C++中数组名的含义指的是什么?

C/C++中数组名的含义指的是什么?

C/C++中的数组名是个很奇怪的东西,它到底代表什么呢?

对于char array[n](n是一个常数),大概有这么几种语义:
<1> char* const(注意不是const char*) <2> char [n]

举例如下(WIN2000 PRO平台,VC.NET 7.1下编译):

<1> char *p = array;//array表示char* const,p得到的是数组的首地址

size_t size = sizeof(char [n]); // size等于n

<2> char (*p)[n] = &array; // array表示char [n],
// p得到的仍然是数组的首地


char (*q)[n] = array; // 编译错误

char (*r)[n] = (char (*)[n])array; // r得到的是array数组的首地址

<3> char (&p)[n] = array; // array表示 char [n]

<4> void foo(char a[n])
{
int size = sizeof(a);
// size == 4(32位系统),
// 因为a实际上表示的是char
*
};

foo(array); // array表示char* const

<5> void foo(char (&a)[n]);
{
int size = sizeof(a);
// size == n
};

foo(array);// array表示char [n]

<6> void foo(char (*a)[n]);
{
int size = sizeof(*a); // size == n
};

foo(&array); // array表示 char [n]

<7> char *p;
array = p;
// 编译错误"error C2440,无法从char*转化为char [n]",
// 因此array表示char [n]

<8> char other[n];
array = other;
// 编译错误"error C2106, '='左操作数必须为L值",
// 因此array表示char [n]

(char (&)[n])array = other;
// 执行完后array的头4个元素表示的32位数与other代表的数组首地址相同,
// 在这里other被解释成char* const

(char (&)[n])array = (char [n])other;
// 执行完后array的头4个元素表示的32位数与other代表的数组首地址相同

(char (&)[n])array = (char (&)[n])other;
// 执行完后array数组与other数组的头四个元素相等

(char (*&)[n])array = (char (*)[n])other;
// (char (*&)[n])表示的是一个引用类型,
// 这个引用关联到一个指向char[n]数组的指针,
// 执行完后array的头4个元素表示的32位数与other代表的数组首地址相同

(__int64&)array = (__int64&)other;
// 执行完后array数组与other数组的头8个元素相等

<9> long i = 0;
(long &)array = i;
// 实际改变的不是array本身的值,
// 而是它代表的数组中的头4个元素(32位),
// 因此array代表的是char [n]

<10> long i = 0;
(char (&)[n])i = array;
// 假设array数组首地址为0x0012feac,
// 则指令执行后i == 0x0012feac

<11> long i = 0;
(char (&)[n])i = (char (&)[n])array;
// 执行后i的值等于array头4个元素代表的32位数(32位系统)

<12> (char *&)array = "string";

// 执行后array头4个元素代表的32位数与
// "string"常量字符串在内存中的地址相同

<13> (char (&)[n])array = (char (&)[n])"string";
// array数组的头4个元素依次为's','t','r','i'

当我们进行(char [n])array这样的强制转换时,效果与(char* const)array转换相当,都被解释成表示数组首地址的指针。但是两者还是有微妙区别的:sizeof(char [n])等于n,sizeof(char* const)等于4(32位系统),而且象(char [m])array这样的转换就不允许,其中m不等于n。如果我们用某种引用类型强制转换数组名时,编译系统会将转换结果(引用类型)自动关联到从数组首地址开始的内存区,而非数组名本身所在的内存区(它是否真的存在于内存中都是个未知数)。当我们用这样强制转换过的数组名做赋值操作的左操作数时,改变的就是数组名代表的数组内存区了,而被改变的内存区的大小就要视引用类型而定,比如__int64&,那么大小就是8字节,其余类推。因为(char [n])(char* const)效果基本相当,结果都被解释成指针,所以(char (&)[n])(char* const &)也基本相当,结果就被解释成关联到指针的引用。当用(char (&)[n])array做赋值操作的右操作数时,实际上会从array数组首地址开始的内存区读sizeof(char* const)大小的数据,然后赋值给左操作数。这就可以解释为什么(char (&)[n])array = (char (&)[n])other执行后array与other的头4个元素相等了。

 

 

补充于2004年12月19日:
我总结的语义恐怕还不够准确,现在把ANSI C标准中的说法摘录如下: (1)当一个数组标识符出现在表达式中,这个标识符的类型就从“某种类型T的数组”转换成“指向类型T的指针”,而且它的值就等于数组第一个元素的地址。但是当数组标识符被用作sizeof和取址(&)操作的操作数时,sizeof返回的是整个数组的大小,而取址操作返回的是指向数组的指针(而不是指向一个值为数组头元素地址的指针的指针)。(2)下列表达式不能产生lvalue: 数组名,函数,枚举常量,赋值表达式,强制类型转换,函数调用。

根据以上说法基本可以推断C程序中数组名的语义,但是这并没有涵盖C++,我手头没有ISO C++标准,所以自己也很不确定。不过可以肯定,C++因为有引用类型,所以情况要复杂。比如对于C中不会产生lvalue的赋值表达式,C++就有不同的解释。

int a;
(a = 10) = 1000;

这两行代码在C中非法,但是在C++中完全合法。

另外,有人说gcc对(5)也会编译报错,经作者用MinGW3.1.0(g++ 3.2.3)测试发现不会。因为(5)中不会产生临时变量,仅仅是用了array来初始化函数中的局部引用类型变量a,这是完全合法的动作。

补充于2005年7月1日: 用MinGW3.1.0(g++ 3.2.3)编译,只要涉及到(char (&)[n])array的强制转换均通不过,错误信息为: cannot convert `char*' to `char[n]' in converting。同样,(char* &)array也出错,因为array是一个类型为char*的rvalue,不允许转换为non-const引用类型,而改成(char* const &)array便可行,但相应的 (char* const &)array = "string" 则对array数组没有任何影响(为什么?估计是因为产生了一个(char * const &)类型的临时变量,对这个变量做初始化,所以对array没有任何影响)。