滴水逆向 03-04 指针7
指向指针的指针:多级指针
*() 与 [] 的互换表示:
以下为方便课堂展示的写法,在实际的开发中,不能直接光定义指针而不赋值(比如只写 char* p1),这样如果后面要使用* p1,由于 p1 没有赋值里面是垃圾数据,用这个数当作地址去取此地址中的值,这个地址可能不是一个正常的地址或者是个不让访问的地址,那么就会产生报错提醒。
-
了解
*的反汇编:1
2
3
4
5
6
7
8
9
10char* p1;
//反汇编 了解*的反汇编
printf("%d\n",*p1);
//反汇编:*p1 = *(p1+0)
printf("%d\n",*(p1+0));
//反汇编:*(p1+0) = p1[0]
printf("%d\n", p1[0]);
三条的反汇编都长这样,都是先取*p1的值作为地址,然后再取这个内存地址中存的值1
printf("%d %d\n",*(p1+2),p1[2]);

Tips:__cdecl调用约定,特点为外平栈+参数从右至左入栈,所以上面那个是p1[2]、下面那个是*(p1+2) -
了解
**的反汇编:1
2
3
4char** p2;
//反汇编:了解**的反汇编
printf("%d\n",*(*p2));
和只有一个*的时候思路一样,只是多了一个*多取了一层,就不细说了1
printf("%d\n",*(*(p2+1))); //char**

因为是**,+1的时候去掉一个*变成char*,所以是+41
2printf("%d\n",*(*(p2+1)+1)); //+4 +1
printf("%d\n",*(*(p2+2)+3)); //+8 +3
+4、+1,+8、+3,没啥好说的1
printf("%d %d\n",*(*(p2+2)+3), p2[2][3]);

在编译器眼里,指针可以用*取值也可以用[]取值,两种写法是完全一样的 -
了解
***的反汇编:1
2
3
4
5
6
7char*** p3;
//反汇编 了解***的反汇编
printf("%d\n",*p3);
printf("%d\n",*(*p3));
printf("%d\n",*(*(*p3)));
三星比两星还多取一次1
2
3
4printf("%d %d\n",*(*(*(p3+1)+2)+3),p3[1][2][3]); //+4 +8 +3
int*** p3; //char改成int
printf("%d %d\n",*(*(*(p3+1)+2)+3),p3[1][2][3]); //+4 +8 +12 -
总结:
*()与[]可以相互转换1
2
3
4*(p+i) = p[i]
*(*(p+i)+k) = p[i][k]
*(*(*(p+i)+k)+m) = p[i][k][m]
*(*(*(*(*(p+i)+k)+m)+w)+t) = p[i][k][m][w][t]
数组指针
数组指针和指针数组的区别:
- 指针数组:本质是数组,只是是指针类型的,be like:
int* p[5] - 数组指针:本质是指针,类似于结构体指针,指向的是一个完整的数组(比如二维数组的某一行),be like:
int (*p)[5] - 内存与大小区别:指针数组存多个指针,
sizeof返回数组总大小;数组指针只存单个地址,sizeof返回指针大小
数组指针的定义与赋值:
1 | int arr[5] = {1,2,3,4,5}; //数组 |
数组指针的特征探测:
1 | int (*px)[5]; //定义数组指针 |
用数组指针获取元素内容:
1 | int arr[16] = { |
这里可能有点绕,为什么 *px 是数组首地址?我们知道 int *p = &a 这个句子里面 *p 是 a 的值,为什么 *px 就不是数组里的值呢?
关键是:px 指向的不是 int,而是有 2 个 int 的数组(px指向 int[2]),类型关系如下:
1 | px // 类型:int (*)[2],指向 int[2] 的指针 |
所以更准确地说,*px 本身是一个 int[2] 数组,只是数组在大多数表达式中会退化为指向首元素的指针,所以我们才说 *px 是数组的首地址。
在本例中,px 和 *px 的值相同,是因为它们都从同一块内存开头开始,但是他们的步长是不一样的,当涉及到加减时,px+1 跳过一整个小数组,也就是 2 个 int,*px+1 只跳过一个 int。
包括后面的计算也是,我觉得把这个数组指针视为大数组和小数组会比较便于理解,海东老师上课讲的根据数据类型计算字节,再换算成有几个 int,然后去数数有点太麻烦了,但还是得看一眼,毕竟计算数据类型的过程也有利于提升对指针的理解,接下来就来看看。
1 | int arr[16] = { |
直接看 *(*(px+2)+1) 吧,这个懂了上面那个也没问题了。首先 px 是 int (*)[2] 类型,去掉一个 * 是 int[2],宽度为 2 个 int 也就是 8 字节,那 px+2 就是 +2×8=16 字节,也就是 4 个 int。然后 *(px+2) 的类型是 int[2],他去参与 +1 运算你把它当成 int* 用,那去掉一个 * 后为 int 型,宽度为 4 字节,即 1 个 int,那么就是从上一步的基础上往后再数 1 个 int 地址,总共走了 5 个 int,最后取值 arr[5] = 6。
补充:int *p[5] 与 int (*p)[5] 有什么区别?回到最开始介绍的问题上了,一个是指针数组一个是数组指针的区别。原因如下:
[]的优先级高于*,所以先组合成p[5]数组,再由int *说明数组存储的类型()的优先级高于[],所以*先和p组合成指针,再由int[5]说明数组的宽度
课后作业
-
*()与[]是可以互换的,也就是说:*(*(p+1)+2)相当与p[1][2]。那*(p+1)[2]是否一定等于p[1][2]呢? 通过反汇编进行论证不一样,因为
[]的优先级比一元*高,p[1][2]应当等价于(*(p+1))[2],而*(p+1)[2]是相当于*((p+1)[2])。1
2
3
4
5(p+1) 类型int (*)[2],指向小数组{3,4}
(p+1)[2] 等价于*((p+1)+2)=*(p+3)
p+3 类型int (*)[2],偏移3组=3×8=24字节=6个int,到arr[6]
*(p+3) 类型int[2],内容是arr[6],arr[7]={7,8}
最外层* 作用在这个int[2]上时退化成int*,再取第一个int所以
*(p+1)[2] == arr[6] == 7关键区别:
1
2p[1][2] // 先 p[1],再 [2]
*(p+1)[2] // 先 (p+1)[2],再外面的 *看一下汇编:

汇编长得不一样,所以确实是俩不一样的 -
使用数组指针遍历一个一维数组
1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, char* argv[]) {
int arr[16] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
};
int (*px)[1];
px = (int (*)[1])arr;
for (int i = 0; i < 16; i++) {
printf("%d ", *(*(px + i)));
}
return 0;
}