滴水-day17-多级指针-数组指针
多级指针
一级指针
// 一级指针
int x = 10;
int* p = &x; // p保存的x的地址
printf("%p %d\\\\n", p, *p);
00A5538F mov dword ptr [ebp-0Ch],0Ah // x
00A55396 lea eax,[ebp-0Ch]
00A55399 mov dword ptr [ebp-18h],eax // p
这个不难理解
二级指针
int a = 30;
int* aP = &a; // aP保存了a的地址
int** aPP = &aP; // aPP保存了aP的地址
006853B3 mov dword ptr [ebp-24h],1Eh // 局部变量a的赋值,a
006853BA lea eax,[ebp-24h] // 取局部变量a的地址放到eax中
006853BD mov dword ptr [ebp-30h],eax // eax保存到局部变量中,aP
006853C0 lea eax,[ebp-30h] // 再取这个局部变量的内存地址保存到eax中
006853C3 mov dword ptr [ebp-3Ch],eax // eax在保存到局部变量中,aPP
三级指针
int b = 40;
int* bP = &b;
int** bPP = &bP;
int*** bPPP = &bPP;
printf("---------------------\\\\n");
printf("b--->%p\\\\n", &b);
printf("bP-->%p\\\\n", &bP);
printf("bPP->%p\\\\n", &bPP);
printf("---------------------\\\\n");
// bPPP-->bPP的地址
printf("bPPP---->%p\\\\n", bPPP);
// *bPPP--->bPP的地址--->bP的地址
printf("*bPPP--->%p\\\\n", *bPPP);
// *bPPP--->bP的地址+*---->b的地址
printf("*(*(bPP)):%p\\\\n", *(*(bPPP)));
// b的地址---->40
printf("*(*(*bPPP)):%d", *(*(*bPPP)));
00D453E5 mov dword ptr [ebp-48h],28h // b
00D453EC lea eax,[ebp-48h]
00D453EF mov dword ptr [ebp-54h],eax // bP
00D453F2 lea eax,[ebp-54h]
00D453F5 mov dword ptr [ebp-60h],eax // bPP
00D453F8 lea eax,[ebp-60h]
00D453FB mov dword ptr [ebp-6Ch],eax // bPPP
有点绕了,但仔细品的话也不难理解。我一开始想不明白的点是:
bPPP既然取得了bP的地址,那么在加一个不就可以取到b的值了吗
其实不然,这里是忘记了bP其实是保存了b的地址,所以在加一个取得是b的地址而不是b的值,所以需要在加一个。
七级指针
太恐怖了,日后再研究。不过我感觉就是套娃
\*与\[]的关系和反汇编
int* m;
int** k;
int*** s
在定义指针的时候一定要赋值,不然会发生野指针。程序不让编译
int v = 20;
int* m = &v;
int** k = &m;
int*** s = &k;
00A454B8 mov dword ptr [v],14h
00A454BF lea eax,[v]
00A454C2 mov dword ptr [m],eax
00A454C8 lea eax,[m]
00A454CE mov dword ptr [k],eax
00A454D4 lea eax,[k]
00A454DA mov dword ptr [s],eax
printf("%d\\\\n", *m);
printf("%d\\\\n", *(m + 0));
001854F4 mov eax,dword ptr [m]
001854FA mov ecx,dword ptr [eax]
001854FC push ecx
001854FD push offset string "%d" (0187BF4h)
00185502 call _printf (01810CDh)
0018550A mov eax,dword ptr [m]
00185510 mov ecx,dword ptr [eax]
00185512 push ecx
00185513 push offset string "%d" (0187BF4h)
00185518 call _printf (01810CDh)
通过这两行代码可以发现m和(m+0)是一样的
printf("%d\\\\n", *(*k));
printf("%d\\\\n", *(*k + 0));
printf("%d\\\\n",k[0][0]);
printf("%p\\\\n", *(k + 2));
printf("%p\\\\n", k[2]);
printf("---------------------\\\\n");
printf("%d\\\\n", *(*(*s)));
printf("%d\\\\n", *(*(*s + 0)));
printf("%d\\\\n", s[0][0][0]);
取值和[]取值是可以互相转换的
k==k[0]
(*k)==k[0][0]
((*(k)==k[0][0][0]
数组指针
语法:
int(*array)[5];
小括号是不能省略的
int ar[] = { 1,2,3,4,5 };
int(*array)[5] = &ar;
// 这两行代码也论证了ar的地址就是数组首个元素的地址
printf("ar的地址:%p\\\\n", &ar);
printf("ar[0]的地址:%p\\\\n", &ar[0]);
// array与*array保存的值是一样的,都是ar数组元素的首地址,但是在实际取值的时候还是要通过**取值
// 这里看似是与*array是一样的值,但实际上array保存的是ar的地址,而ar的地址被看作是数组元素的首地址
// 而*array是取ar地址指向的数据的地址(就是数组元素的首地址了,还是本身)。
printf("arra保存的数据:%p\\\\n", array);
// 此时array是数组元素首地址在添加一层取值可以取到这个元素
printf("解引用array后数据:%p\\\\n", *array);
printf("%d\\\\n", *(*(array) + 1));
printf("%d\\\\n", sizeof(array));
array++; // 加4
printf("%p", array);
数组指针说白了就是保存数组变量名称的地址,而数组变量名称的地址就是数组首元素的地址
所以array与*array看起来值是一样的,但意思不一样,取array的值看到的只是ar的地址。
而*array取到的是数组元素的首地址。只是他俩的地址一样,容易导致混淆。
在给*array加一层取值就可以取到数组首个元素的值了。
array是可以进行偏移的,偏移几个int字节就是往后查几个数。
比如:
((array) + 1)
就是取ar的首地址在加1,而加1就相当于加了一个int,所以往后查1个int就行
在经过*取值,取到的就是2.
切记不能使得array+1,因为这样偏移的是指针,这样会导致array不在指向当前数组而是指向其他东西。
除非数组指针宽度小于所指向的数组宽度。
int(*array)[5] = (int(*)[5]) & ar;
//int(*array)[5] = (int(*)[5])ar;
这两种代码在实际使用中效果一样,但是他们的类型是不同的,&ar表取ar的地址即整个数组,而ar只是表示取数组首个元素的地址。
int arra[] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17};
int(*arrP)[2] = (int(*)[2]) & arra;
printf("arra[2]:%p\\\\n", &arra[2]);
printf("(arrP + 1):%p\\\\n", arrP + 1); // 这里也论证了arrP+1就是取了数组第三个元素的地址
printf("%d\\\\n", *(*(arrP + 0) + 0));
printf("%d\\\\n", *(*(arrP + 1) + 1));
printf("%d\\\\n", arrP[0][1]);
这里也不难理解为何arrP可以进行加发,因为arrP是长度为2的int类型数组指针,他一次只能指向两个元素。
当对arrP进行加1时实际上加的是8字节,2个int,就往后查两个。就是3的地址
((arrP + 0) + 0)可以写为arrP[0][0]
arrP[0][2]虽然也可以正确的取到第三个元素,但这里已经发生了数组越界行为,因为arrP是长度为2的。
若想正确的读取第三个元素,要这样写
arrP[1][0]
使用数组指针:((arrP + 0) + 3)不会发生越界。
那*(p+1)[2] 是否一定等于p[1][2]呢? 通过反汇编进行论证。
不一定
00165914 mov eax,8
00165919 shl eax,1
0016591B mov ecx,dword ptr [arrP]
00165921 lea edx,[ecx+eax+8]
00165925 mov eax,4
0016592A imul ecx,eax,0
0016592D mov edx,dword ptr [edx+ecx]
00165930 mov dword ptr [xx],edx
00AE5936 mov eax,8
00AE593B shl eax,0
00AE593E add eax,dword ptr [arrP]
00AE5944 mov ecx,4
00AE5949 shl ecx,1
00AE594B mov edx,dword ptr [eax+ecx]
00AE594E mov dword ptr [ss],edx
解释:
(p+1)[2]为何取到的值是7而不是5?
PS:[]优先级高于*
p是数组首地址的指针,且是一个int(*)[2]类型
当p+1的时候实际上是指针往后走了两个int类型的宽度,就来到了数组第三个元素的位置,此时p指向这里。
当p+1执行完毕以后并没有接着执行而是先执行了[2],p+1执行完毕之后还是int()[2]类型。p+1的结果暂且记为:A
A[2],这个就好理解了A[2]就相当于*((A)+2));
A是int()[2]类型,加2就是加了82=16个字节,就是4个int
数组第三个元素再往后查4个int,就是7咯。
printf("%p\\\\n", &arra[6]);
printf("%p\\\\n", (arrP + 1)[2]);
这两行代码也论证了(arrP + 1)[2]取到的就是arra[6]的地址,再加一个*取值就完事了。
同时汇编代码中也看出,汇编是同时计算了[2]和p+1.
ecx是数组首地址,eax是[2]的结果就是4个int类型,在+2个int类型。数组首地址往后查6个int类型取到的就是7的地址咯。
那么p[1][2]就不难理解了
p[1]就是p首地址往后查2个int类型就是3的地址,3在往后查两个int就是5咯
p[1]就是取到了3这个地址,是int*类型
int*类型在+2就是加了2个int类型。
PS:p[1]与p+1执行结果的类型是不一样的,p[1]是取数组元素的地址,而p+1是让p往后走,走完之后的类型还是int(*)[2];