转自:http://blog.dreambrook.com/soloist/archive/2004/11/12/331.aspx
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个元素相等了。
以下转自: http://blog.csdn.net/hustli/archive/2003/07/07/19363.aspx
在内存中,指针的分配只是占用了4个字节的空间(不要考虑16位等其他情况,毕竟最常见的是32位的,嗯,好吧,我们随后就谈谈这方面的问题),但是对于数组就不同了,它除了要分配自己的内存单元,还要保存一些信息去存储它的元素的个数。从理论上来讲,编译器可以采用任何技术,但是在实际的编译器上,一般采用2种比较流行的手法:关联手法、Cookie策略。
关联手法,就是分配一个static数组,有编译器去控制,里面放上一个map,关键字可以采用数组名+有效区间,值则可以是元素个数,释放数组内存空间的时候,就去搜索相应的map,看起来这是一个比较安全的策略,但是也比较的慢。
Cookie策略,也称过渡分配技术,没进行一次数组空间分配的时候,就在数组前面的某个位置加上一个sizeof(size_t)字节的空间,来存放数组的元素个数。这个策略毫无疑问很快,但是不够安全,有的时候,我们可以取到Cookie的值,并进行修改,这样一来就可以破坏堆栈空间。
不过,这里有一个例外,在数组被当作参数传递的时候,数组会被自动将为普通指针,这样做的好处也很明显:提高效率。你可以想象,如果不当作指针传递,那么元素的传递将是一笔大的开销。
指针还有一个很要命的问题,那就是指针的对齐问题。
当你在80x86处理器上执行你的程序时,这个问题不是致命的,但对其他的绝大多数芯片来说,这就是致命的了。它还会对你的应用程序移植到某个其他环境的能力产生影响。此外,甚至对于Intel 处理器来说,这个问题也许不是致命的,但是她会占用CPU更多的时间进行转换,毫无疑问的导致性能的下降。
当你的指针从一种类型转换到另一种类型的时候,就有可能产生一个非对准指针(misaligned pointer)。处理器一般要求内存块的地址要与一个和这个内存块的尺寸匹配的边界对齐。例如,字只能在字边界上被访问(地址是二的倍数),双字只能在双字边界上被访问(地址是四的倍数),依次类推。
编译器通常确保监视这个规则。但是当你的指针类型从一种类型转换成较大类型时,你就可以很容易地违反这个规则:
char cA;
char* pC = &cA;
int* pI;
pI = (int*)pC;
*pI = 0;
因为字符仅仅是一个字节长,所以地址&cA可能有任意值,包括奇数值。可是,pI应只包含四的倍数的地址。通过转换,允许把pC赋给pI,但是如果地址不是四的倍数,则接着发生的赋值可能使程序崩溃。
不过,这种情况只在你正在把你的指针从指向一种类型转换成指向较大类型时才会出现。