在讨论这个问题前, 我们先看一段代码。这段代码,大家都会很容易看懂, 但是真正能够明白的我不知道有几。
/* 代码 1-1 */
#include <stdio.h>
int main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
return 0;
}
输出结果为: 2, 5
讨论:
当我第一眼看到这个代码的时候, 我的第一直觉答案是2, 1. 但是当运行结果出来以后, 我蒙了。为什么会是2, 5 呢。后来我就测试了输出他的地址(原来最终问题,通过查看内存才是王道),我就发现了问题的所在。主要的原因就在于 &a + 1 这里。这里给出我查看内存运行情况的代码:
/* 代码1-2 */
#include <stdio.h>
int main()
{
int c[6] = {1, 2, 3, 4, 5, 6, };
printf("c = %p, c+1 = %p, &c+1 = %p\n", c, c+1, &c+1);
/* 数组名c 的地址, 数组名c + 1 的地址, 数组名地址 + 1 的地址*/
return 0;
}
运行结果: c = 0022FF18, c+1 = 0022FF1C, &c+1 = 0022FF30
通过这个输出结果, 我们不难看出,c+1 的地址是在数组地址(c 的地址)的基础上加了1 个int。但是,&c + 1 的地址是在数组地址(c 的地址)的基础上加了6 个int 。而这6 个int 就是整个数组的长度。这不是偶然,这是必然。就是说,在数组地址的基础上加 1 就相当于再加了一个数组(跳过一个数组的长度)。
这样我们就不难解释上面代码 1-1,为什么结果是2, 5 。
int *ptr=(int *)(&a+1);
这里的ptr 地址实际上就是跳到整个数组的最末端,然后新开始的一个数组(即元素5 后面的一个位置)。所以,当输出*(ptr-1) 就是5 。
讨论了一维数组的情况, 下面我们接着看看二维数组。
按理说,把一维数组弄明白了二维数组就很简单,这里因为笔者在后面写二维数组的时候也遇到了点麻烦,所以就多多啰嗦一下。
下面我们看一段测试代码:
/* 代码 2 - 1 */
#include <stdio.h>
int main()
{
int a[][3] = {{1, 2, 3, }, {4, 5, 6, }};
// 数组名的不同 +1
printf("结果1\n");
printf("** a = %d\n", **a);
printf("a = %p\n", a);
printf("&a = %p\n", &a);
printf("a+1 = %p\n", a+1);
printf("&a+1 = %p\n", &a+1);
printf("\n");
// 数组起始单元的不同+1
printf("结果2\n");
printf("* a[0] = %d\n", *a[0]);
printf("&a[0] = %p\n", &a);
printf("a[0]+1 = %p\n", a[0]+1);
printf("&a[0]+1 = %p\n", a[0]+1);
printf("\n");
// 数组中间单元不同+1
printf("结果3\n");
printf("a[0][1] = %d\n", a[0][1]);
printf("&a[0][1] = %p\n", &a[0][1]);
printf("&a[0][1]+1 = %p\n", &a[0][1]+1);
return 0;
}
输出结果为:
结果1
** a = 1
a = 0022FF18 // 数组名
&a = 0022FF18 // 数组名地址
a+1 = 0022FF24 // 数组名加1 , 结果为跳转到下一行(即第二行,这里跳过3 个int)开头。
&a+1 = 0022FF30 // 数组名地址加1, 按照上文介绍,跳转到下一个数组的位置(即跳过整个数 组,如果是加2 , 那么就跳过2 个数组空间的位置。),这里就跨过了6 个int 的空间。
结果2
* a[0] = 1
&a[0] = 0022FF18 // 数组中第一行行名地址
a[0]+1 = 0022FF1C // 数组中第一行行名加1, 结果为跳转到同行的下一个元素位置(跳过1 个int)
&a[0]+1 = 0022FF24 // 数组中第一行行名地址加1, 结果为跳转一行的地址,到下一行的行头位置 (即跳转了3 个int 位置)。
结果3
a[0][1] = 2
&a[0][1] = 0022FF1C // 数组中位置为[0][1] 的地址
&a[0][1]+1 = 0022FF20 // 数组中位置为[0][1] 的地址加1 ,结果跳转了1 个int 的位置。跳转到同行的下一 个元素位置。是本行的行末位置,那么就跳转到下一行的开头位置。
这段代码有点多, 都是printf(); 这个不重要,重要的是输出的内容。分析就在答案的后面注释。
从这里的结果, 我们可以看出 结果3 中的 &a[0][1]+1 = 0022FF20 与 结果1, 结果2中的 &a+1 = 0022FF30 , &a[0]+1 = 0022FF24 运算方式是不同的。这是因为,结果1和结果2 中是对数组名地址加1, 而结果3 不是 对数组名地址加1, 他只是数组中间的一个元素地址。如果要进一步深究,我们可以再把指针带进来考虑就可以更加清晰明白。
指针的操作,就相当于上面 结果3 的操作结果一样。他的操作都是按部就班,不会存在因为加1 而跳转一个数组的长度的情况。下面我们看看测试代码:
/* 代码 3 -1 */
#include <stdio.h>
int main()
{
int a[][3] = {{1, 2, 3, }, {4, 5, 6, }};
int *p1 = *a; // 取a 的地址
int *p2 = a[0]; // 取a[0] 的地址
int *p3 = &a[0][1]; // 去a[0][1] 的地址
printf("p1 = %p \n", p1);
printf("p1+1 = %p \n", p1+1);
printf("p2 = %p \n", p2);
printf("p2+1 = %p \n", p2+1);
printf("p3 = %p \n", p3);
printf("p3+1 = %p \n", p3+1);
return 0;
}
输出结果:
p1 = 0022FF0C
p1+1 = 0022FF10
p2 = 0022FF0C
p2+1 = 0022FF10
p3 = 0022FF10
p3+1 = 0022FF14
从这个结果可以看出指针的跳转是按位置跳转的。一次只能跳转偏移个数个位置。
比如: int *p = a; p + 3; 就表示跳转从当前p 的位置, 跳转到之后的第三个位置。
总结:
综上所诉,笔者最想表达的是,在数组名取地址然后增加偏移量时, 他是按照整个数组再进行跳转。即跳过整个数组的长度的偏移量倍。