#include <stdio.h>
void main()
{
int a[2][3]={1,2,3,4,5,6};
int b=0;
int c=0;
// b=*((int*)&a[1]+2); //OK
// b=*(a[1]+2);//ok
// b=*(*(a+1)+2);//ok
b=*((int*)((int)&a[1]+2*sizeof(int)));//ok
printf("%d\n",b);
// 结果均是:6
// c=*(&a[1]+2*sizeof(int));// c!=a[1][2]
// printf("%d\n",c);
// 结果:1245140(十六进制为:12FFD4)
// c=*((int)&a[1]+2*sizeof(int));//error C2100: illegal indirection
// c=*(&a[1]+2);//c!=a[1][2]
printf("%d\n",c);
// 结果:1245068(十六进制为:12FF8C)
}
结论:拿int a[2][3]={1,2,3,4,5,6}来说,&a其值是数组的首地址不是数组首元素的首地址(虽然它和首元素的首地址相同),&a的类型是int (*)[2][3],所以当&a+n时,实际是按照&a(计算的过程中把&a认为是地址值,是个无符号整型变量啦,不再考虑它的类型啦)+n*sizeof(a),来得到的新地址(也是无符号整型变量),这个新地址肯定是数组a的界外,计算结果的类型还是 int (*)[2][3]。其中sizeof(a)是代表数组占的内存大小(就是数组a所有元素占的总字节数)。
而a(作右值时)和&a[0],他们值相同,类型也相同(或相当),都是二维数组a第一个元素a[0](它也是个数组,是个长度为3的一维数组)的首地址(就是一维数组的首地址),类型为int [][3]也即int (*)[3](一维数组的指针),所以a,&a[0],&a[1](a第二个元素)的类型是相同(相当)的。所以当a+1或&a[0]+1时,他们均代表着a(计算的过程中a是元素a[0]地址值,是个无符号整型变量啦,不再考虑它的类型啦。下同)或&a[0](是地址值,是个无符号整型变量啦) +1*sizeof(a[0])后的地址值,结果就是a的第二个元素a[1](长度为3的一维数组)的首地址,而且它的类型还是int(*)[3],其中sizeof(a[0])是求数组a中第一个元素a[0]这个一维数组的占的内存大小,不能用sizeof(a)。
而a[0]和&a[0][0],他们的值和类型都是相同的,都是二维数组a第一个元素中含的第一个元素的首地址,类型是int [3],也即是int *。
类似的,a[1]和&a[1][0],他们的值和类型都是相同的,都是二维数组a第二个元素中含的第一个元素的首地址,类型是int [3],也即是int *。
所以 a[0],a[1],&a[0][0],&a[0][1],&a[0][2],&a[1][0],&a[1][1],&a[1][2]的类型相同,都是int *。所以当a[0]+2或&a[0][0]+2,他们均代表着二维数组a第一个元素中含的第一个元素的首地址+2*sizeof(a[0][0])后得到地址,也就是按照&a[0][0]) (二维数组a的第一个元素中含的第一个元素的首地址)+2*sizeof(int),因为数组是int类型的,就是元素a[0][2]的地址&a[0][2]。
而a[0][0],a[1][2]这样的的类型显然是int。
通过上面的总结再次验证啦这样一条规定:数组名是其元素(如果数组是n维数组,那么这里的元素就是指每一个n-1维数组)的首地址,而不是本数组的首地址。例如:int a[2][3]={1,2,3,4,5,6}; (数组名)a不是数组a的首地址(数组a的首地址是&a),而是元素a[0](长度为3的一维数组)的首地址。(一维数组名)a[0]是数组a[0]的第一个元素a[0][0]的首地址,而不是数组a[0]的首地址(数组a[0]的首地址是a,或&a[0])。
###############################################
###############################################
以下转自于《C语言深度解剖》
另外有如下2个例子:
main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
}
对指针进行加1 操作,得到的是下一个元素(这里的元素是指针所指向的那种类型,可能是单个char类型,int类型,甚至是数组,结构体等)的地址,而不是原有地址值(地址值是无符号整型类型)直接加1。所以,一个类型为T 的指针的移动,以sizeof(T) 为移动单位。因此,对上题来说,a 是一个一维数组,数组中有5 个元素; ptr 是一个int 型的指针。
&a + 1: 取数组a 的首地址,该地址的值加上sizeof(a) 的值,即&a + 5*sizeof(int),也就是下一个数组的首地址,显然当前指针已经越过了数组的界限。
(int *)(&a+1): 则是把上一步计算出来的地址(地址值),强制转换为int * 类型,赋值给ptr。
*(a+1): a,&a 的值是一样的,但意思不一样,a 是数组首元素的首地址,也就是a[0]的首地址,&a 是数组的首地址,a+1 是数组下一元素的首地址,即a[1]的首地址,&a+1 是下一个数组的首地址。所以输出2
*(ptr-1): 因为ptr 是指向a[5],ptr的值实际上和&a[5]相等,显然a[5]在数组a里并不存在,只是可以这样理解,所以显然当前指针已经越过了数组的界限。并且ptr 是int * 类型,所以*(ptr-1) 是指向a[4] ,
输出5。
在x86 系统下,其值为多少?
intmain()
{
int a[4]={1,2,3,4};
int *ptr1=(int *)(&a+1);
int *ptr2=(int *)((int)a+1);
printf("%x,%x",ptr1[-1],*ptr2);
return 0;
}
根据上面的讲解,&a+1 与a+1 的区别已经清楚。
ptr1:将&a+1 的值强制转换成int*类型,赋值给int* 类型的变量ptr1,ptr1 肯定指到数组a之后的,紧挨着数组a的一个占4字节(因为ptr指向int类型的数据)大小的内存的首地址。ptr1[-1]被解析成*(ptr1-1),即ptr1 往后退4 个byte(1个int类型数据的大小),就是指向数组元素a[3],所以其值为0x4(十六进制)。
ptr2:按照上面的讲解,(int)a+1 的值是元素a[0]的第二个字节的地址。然后把这个地址强制转换成int*类型的值赋给ptr2,也就是说*ptr2 的值应该为元素a[0]的第二个字节开始的连续4 个byte 的内容。这4个字节内存跨了a[0],a[1]两个元素,这连续4 个byte 里到底存了什么东西呢?也就是说元素a[0],a[1]里面的值到底怎么存储的。这就涉及到系统的大小端模式了。既然不知道当前系统是什么模式,那就得想办法测试。,我们完全可以利用union 类型数据的特点:所有成员的起始地址一致来测试系统是什么模式。
我们可以用下面这个函数来测试当前系统的模式。
int checkSystem( )
{
union check
{
int i;
char ch;
} c;
c.i = 1;
return (c.ch ==1);
}
如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。
也就是说如果此函数的返回值为1 的话,*ptr2 的值为0x2000000。
如果此函数的返回值为0 的话,*ptr2 的值为0x100。
最后还要补充说明的一点:其详细内容可以参考:http://blog.csdn.net/rjzou2006/archive/2008/04/15/2292698.aspx
数组名不完全等于指针
. 数组什么时候会"退化"
下面是C99中原话:
Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue.
上面这句话说的很清楚了, 数组在除了3种情况外, 其他时候都要"退化"成指向首元素的指针.
比如对 char s[10] = "china";
这3中例外情况是:
(1) sizeof(s)
(2) &s;
(3) 用来初始化s的"china";
除了上述3种情况外,s都会退化成&s[0], 这就是数组变量的操作方式