记得当初学习指针的时候,总是把指针和地址混为一谈,总以为说到指针就是指某个地址而已,后来加强对各类指针的认识以后,才认识到指针不止是简单的地址。
指针是一种类型,通过类型可以声明一个变量并保存一个内存地址,不同类型的指针对保存的地址会有不同的解释,编译器根据指针的类型(对应的偏移量)解引用出相应的数据。
首先在32位程序设计里,指针大小为4bytes,满足2^32 寻址范围。
到底偏移多少:
曾经探究过一个问题,代码如下:
int a[4][2] = { 0, 1, 2, 3, 4, 5 ,7,8 }; printf("a = %p,a + 1 = %p\n", a, a + 1); printf("range = %d\n", a + 1 - a);
运行结果:
a = 00E7FC58, a + 1 = 00E7FC60
range = 1
问题来了,为何a + 1 - a 不等于8,等于1呢?
汇编代码:
printf("range = %d\n", a + 1 - a); 00035DDF lea eax,[ebp-20h] 00035DE2 lea ecx,[a] 00035DE5 sub eax,ecx 00035DE7 sar eax,3 00035DEA mov esi,esp 00035DEC push eax 00035DED push 3DA54h 00035DF2 call dword ptr ds:[41204h]
即,编译器对减去过后的eax进行处理,eax = 8 ,sar eax,3 后eax右移三位编程最后结果1。
思考:
对于指针的操作,编译器是不是早已内嵌完成一套偏移运算呢?
我们来看以下代码:
int a[4][2] = { 0, 1, 2, 3, 4, 5 ,7,8 }; printf("*(*(a + 0) + 0) = %d\n", *(*(a + 0) + 0)); printf("*(*(a + 1) + 0) = %d\n", *(*(a + 1) + 0)); printf("*(*(a + 1) + 1) = %d\n", *(*(a + 1) + 1));
输出结果很简单,但是在指针解引用编译器如何处理呢?
printf("*(*(a + 0) + 0) = %d\n", *(*(a + 0) + 0)); 00DF5DC5 push eax printf("*(*(a + 1) + 0) = %d\n", *(*(a + 1) + 0)); 00DF5DDB mov esi,esp 00DF5DDD mov eax,dword ptr [ebp-20h] printf("*(*(a + 1) + 1) = %d\n", *(*(a + 1) + 1)); 00DF5DF6 mov esi,esp 00DF5DF8 mov eax,dword ptr [ebp-1Ch]
可见,编译器自动找到了相应的地址并取出了我们需要的元素,内嵌完成了一套寻址的偏移运算。
探究:
以下代码偏移规则是怎样的?
int a[4][2] = { 0, 1, 2, 3, 4, 5 ,7,8 }; printf("*(a + 0) + 0 = %p\n", *(a + 0) + 0); printf("*(a + 1) + 0 = %p\n", *(a + 1) + 0); printf("*(a + 1) + 1 = %p\n", *(a + 1) + 1);
结果:
*(a + 0) + 0 = 0060FE1C
*(a + 1) + 0 = 0060FE24
*(a + 1) + 1 = 0060FE28
实质上 *(a ) + m是一个 int* 类型,所以每加1偏移也就是 4bytes。
再来看:
int a[4][2] = { 0, 1, 2, 3, 4, 5 ,7,8 }; printf("a = %p\n", a); printf("a + 1 = %p\n", a + 1);
结果:
a = 00C7FEA0
a + 1 = 00C7FEA8
实质上a 是一个 int (*)[2]指针,偏移就是 2 * 4bytes。
再来看:
int a[4][2] = { 0, 1, 2, 3, 4, 5 ,7,8 }; printf("&a = %p\n", &a); printf("&a + 1 = %p\n", &a + 1);
结果:
&a = 00C2F96C
&a + 1 = 00C2F98C
实质上&a是一个 int (*)[4][2]指针,&a + 1也就偏移了32bytes,跨越了一个a[4][2]的长度,
总结:
我在想,用什么样的方式才能很好解释指针偏移这个问题,想到一点就是:你需要判断这个指针到底是一个什么类型,我们通过指针类型就可以轻松算出偏移大小。