1.定义
指针:在K&R C上的原文为“A pointer is a group of cell (often two or four) that can hold an address.” 从概念上看,指针是包含地址的变量。
数组:能存储连续固定数量的相同类型元素的数据结构。
2.指针数组和数组指针
int *arr_for_p[5]; // 指针数组 int (*p_for_arr)[5]; // 数组指针
由于[]的优先级高于*,所以第一条语句可以写成这样
int* (arr_for_p[5]);
而第二条语句的解释可以理解为(*p_for_arr)代表了一个5个整数的数组,而p_for_arr就是这个数组的指针,也就是这个数组首地址,所以使用起来类似于(*p_for_arr)[i]。实际上p_for_arr和*p_for_arr是同一个值,因为指针变量指向的是数组的首地址,而指针变量取消引用之后得到的是这个数组(实质上也是数组的首地址),所以它们的值是一样的。
3.数组首地址与数组首元素的首地址
#include <stdio.h> int main(void) { int a[5]; int (*ptr_1)[5] = a; printf("ptr_1 = %p(%u) ", ptr_1, ptr_1); int (*ptr_2)[5] = &a; printf("ptr_2 = %p(%u) ", ptr_2, ptr_2); return 0; } /* 结果: ptr_1 = 0xbfc9a144(3217662276) ptr_2 = 0xbfc9a144(3217662276) */
从C的层面上理解a是数组首元素的首地址,而&a是数组的地址,很显然在栈上分配的数组是一段连续的内存,这里是20个字节,那么可以知道a和&a的值就是一样的——都是这段内存的起始地址,去掉打印语句我们得到的汇编代码如下
main: pushl %ebp movl %esp, %ebp subl $32, %esp leal -28(%ebp), %eax # a movl %eax, -8(%ebp) leal -28(%ebp), %eax # &a movl %eax, -4(%ebp) movl $0, %eax leave ret
在汇编层面上它们就是相同的值,都是%ebp-28。
4.数组指针运算
#include <stdio.h> int main(void) { int a[5]; int (*ptr_a)[3] = a; int (*ptr_b)[5] = a; int (*ptr_c)[10] = a; unsigned long tmp = a; printf("tmp = %p(%d) ", tmp, tmp); tmp = ptr_a+1; printf("tmp = %p(%d) ", tmp, tmp); tmp = ptr_b+1; printf("tmp = %p(%d) ", tmp, tmp); tmp = ptr_c+1; printf("tmp = %p(%d) ", tmp, tmp); return 0; } /* 结果: tmp = 0020FAA8(2161320) tmp = 0020FAB4(2161332) tmp = 0020FABC(2161340) tmp = 0020FAD0(2161360) */
可以看到三个数组的指针加1后的偏移都不同,分别是12,20和40,所以它们是以数组为单位偏移,而这些都是编译器的工作。
5.多维数组
本质上多维数组和一维数组并无二致,只是进行多次下标运算而已,看下面的代码
int arrs[10][10]; printf("arrs = %p ", arrs); printf("arrs[0] = %p ", arrs[0]); printf("&arrs[0][0] = %p ", &arrs[0][0]); /* * 在Visual Studio 2010 中显示结果为: * arrs = 0031FBB4 * arrs[0] = 0031FBB4 * &arrs[0][0] = 0031FBB4 */
6.多级指针
多级指针本质上也是指针,也就是含有地址的变量,关键是这个地址要合法。
7.多级指针与多维数组
我个人认为这两者没有什么必然的联系,除非动态分配多维数组,不过始终记住多维数组下标运算编译器采用的是首地址偏移的方式,而多级指针下标运算采用的是指针多次解除引用的方式,二者完全不同。请看下面的代码
int main(void) { int arrs[3][4]; int** pparrs = (int **)arrs; arrs[2][2] = 33; pparrs[2][2] = 33; return 0; }
汇编代码如下
main: pushl %ebp movl %esp, %ebp subl $64, %esp leal -52(%ebp), %eax # 下面是直接偏移寻址 movl %eax, -4(%ebp) movl $33, -12(%ebp) movl -4(%ebp), %eax # 下面是取消多级引用 addl $8, %eax movl (%eax), %eax addl $8, %eax movl $33, (%eax) movl $0, %eax leave ret
当然上面的代码直接运行肯定会宕掉。