指针相关知识

指针相关知识

0x01 介绍

本篇文章主要记录一下自己学习的指针的部分相关知识,如有错误的地方还请指正。

0x02 环境

Visual C++ 6.0

0x03 &符号的使用

&是地址符,要想了解它的用法,我这里先写一段简单的测试代码来进行观察,如下所示:

#include "stdafx.h"

void Test()
{
    char a = 10;
    char* pa;

    pa = &a;

}

int main(int argc, char* argv[])            
{    
    Test();

    return 0;        
}

这里可以对Test()或者return 0处下断点(或直接在pa = &a处下断),然后编译运行观察反汇编,如下所示:

00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,48h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-48h]
0040102C   mov         ecx,12h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
00401038   mov         byte ptr [ebp-4],0Ah
0040103C   lea         eax,[ebp-4]             //取局部变量的地址放到eax中
0040103F   mov         dword ptr [ebp-8],eax   //将eax中的值放入到另一个局部变量里
00401042   pop         edi
00401043   pop         esi
00401044   pop         ebx
00401045   mov         esp,ebp
00401047   pop         ebp
00401048   ret

通过上述反汇编代码我们可以看到0040103C和0040103F即为pa = &a的反汇编代码,可以看到&符号就是用来取地址的,任何变量都可以使用&来获取地址,它的类型是其后面的类型加一个“ * ”。
下面再看一段代码及其反汇编,如下所示:

char a = 10;                             mov byte ptr [ebp-0x4],0xA
short b = 20;                             mov word ptr [ebp-0x8],offset test+20h
int c = 30;                                 mov dword ptr [ebp-0xc],0x1E

char* pa = &a;                             lea eax,[ebp-0x4]
                                         mov dword ptr [ebp-0x10],eax
short* pb = &b;                             lea eax,[ebp-0x8]
                                         mov dword ptr [ebp-0x14],eax
int* pc = &c;                             lea eax,[ebp-0xC]
                                         mov dword ptr [ebp-0x18],eax

char** ppa = &pa;                         lea eax,[ebp-0x10]
                                         mov dword ptr [ebp-0x1C],eax
short** ppb = &pb;                         lea eax,[ebp-0x14]
                                         mov dword ptr [ebp-0x20],eax
int** ppc = &pc;                         lea eax,[ebp-0x18]
                                         mov dword ptr [ebp-0x24],eax

通过上述代码,可以看到无论变量的类型是什么,&符号都是来获取地址的,其反汇编代码也是固定的这两行(注意&符号不能用在常量上)。
然后再看一组代码和反汇编,如下:

int p = 10;                                 mov dword ptr [ebp-4],0xA

int******* p7;            
int****** p6;            
int***** p5;            
int**** p4;            
int*** p3;            
int** p2;            
int* p1;            

p1 = &p;                                 lea eax,[ebp-0x4]
                                         mov dword ptr [ebp-0x20],eax
p2 = &p1;                                 lea eax,[ebp-0x20]
                                         mov dword ptr [ebp-0x1C],eax
p3 = &p2;                                 lea eax,[ebp-0x1C]
                                         mov dword ptr [ebp-0x18],eax
p4 = &p3;                                 lea eax,[ebp-0x18]
                                         mov dword ptr [ebp-0x14],eax
p5 = &p4;                                 lea eax,[ebp-0x14]
                                         mov dword ptr [ebp-0x10],eax
p6 = &p5;                                 lea eax,[ebp-0x10]
                                         mov dword ptr [ebp-0xC],eax
p7 = &p6;                                 lea eax,[ebp-0xC]
                                         mov dword ptr [ebp-0x8],eax

可以看到当 * 的数量增多的时候,&符号的反汇编语句还是这两行。

0x04 带 * 类型求值

我们还是先来看一段代码(其他代码已省略),如下:

void Test()
{
    int x = 10;

    int* px = &x;

    int x1 = *px;

}

在 int x1处下断点,编译运行,查看反汇编,如下所示:

0040F000   push        ebp
0040F001   mov         ebp,esp
0040F003   sub         esp,4Ch
0040F006   push        ebx
0040F007   push        esi
0040F008   push        edi
0040F009   lea         edi,[ebp-4Ch]
0040F00C   mov         ecx,13h
0040F011   mov         eax,0CCCCCCCCh
0040F016   rep stos    dword ptr [edi]
0040F018   mov         dword ptr [ebp-4],0Ah     //将0xA放到局部变量中
0040F01F   lea         eax,[ebp-4]               //取局部变量的地址放到eax中
0040F022   mov         dword ptr [ebp-8],eax     //将eax中的值放在另一个局部变量中
0040F025   mov         ecx,dword ptr [ebp-8]     //将第二个局部变量中的值放到ecx中
0040F028   mov         edx,dword ptr [ecx]       //将ecx当做地址编号,取其中的值放到edx中
0040F02A   mov         dword ptr [ebp-0Ch],edx   //将edx中的值放到第三个局部变量中
0040F02D   pop         edi
0040F02E   pop         esi
0040F02F   pop         ebx
0040F030   mov         esp,ebp
0040F032   pop         ebp
0040F033   ret

通过上述分析,可以得出*px的值的类型为int,它的值为10。
接着将上述代码改为以下内容:

void Test()
{
    int x,y;    
    int* px;    
    int** px2;    
    int*** px3;    

    x = 10;    
    px = &x;    
    px2 = &px;    
    px3 = &px2;    

    y = *(*(*(px3)));

    printf("%d\n",y);
}

在y处下断,编译运行并观察反汇编,如下所示:

0040F000   push        ebp
0040F001   mov         ebp,esp
0040F003   sub         esp,54h
0040F006   push        ebx
0040F007   push        esi
0040F008   push        edi
0040F009   lea         edi,[ebp-54h]
0040F00C   mov         ecx,15h
0040F011   mov         eax,0CCCCCCCCh
0040F016   rep stos    dword ptr [edi]
0040F018   mov         dword ptr [ebp-4],0Ah      //将0xA放到局部变量[ebp-4]中
0040F01F   lea         eax,[ebp-4]                //取[ebp-4]的地址放到eax中
0040F022   mov         dword ptr [ebp-0Ch],eax    //将eax的值放到局部变量[ebp-0Ch]中
0040F025   lea         ecx,[ebp-0Ch]              //取[ebp-0Ch]的地址放到ecx中
0040F028   mov         dword ptr [ebp-10h],ecx    //将ecx的值放到局部变量[ebp-10h]中
0040F02B   lea         edx,[ebp-10h]              //取[ebp-10h]的地址放到edx中
0040F02E   mov         dword ptr [ebp-14h],edx    //将edx的值放到局部变量[ebp-14h]中
0040F031   mov         eax,dword ptr [ebp-14h]    //将[ebp-14h]的值放到eax中
0040F034   mov         ecx,dword ptr [eax]        //将eax当做地址编号,取其中的值放到ecx中
0040F036   mov         edx,dword ptr [ecx]        //将ecx当做地址编号,取其中的值放到edx中
0040F038   mov         eax,dword ptr [edx]        //将edx当做地址编号,取其中的值放到eax中
0040F03A   mov         dword ptr [ebp-8],eax      //将eax中的值放到局部变量[ebp-8]中
0040F03D   mov         ecx,dword ptr [ebp-8]      //将[ebp-8]的值放到ecx中
0040F040   push        ecx
0040F041   push        offset string "%d\n" (0042401c)
0040F046   call        printf (0040f280)
0040F04B   add         esp,8
0040F04E   pop         edi
0040F04F   pop         esi
0040F050   pop         ebx
0040F051   add         esp,54h
0040F054   cmp         ebp,esp
0040F056   call        __chkesp (004011c0)
0040F05B   mov         esp,ebp
0040F05D   pop         ebp
0040F05E   ret

通过上述分析,可以得出最后局部变量[ebp-8](也就是y)中的值为10。说明带 * 类型的变量,可以通过在其变量前加 * 来获取其指向内存中存储的值,在带 * 类型的变量前面加 * ,其类型是其原来的类型减去一个 * 。

0x05 用指针操作数组

之前我们遍历数组可能会写成以下这种形式:

void Test()
{
    char arr[5] = {1,2,3,4,5};        

    for(char i=0;i<5;i++)
    {
        printf("%d\n",arr[i]);
    }
}

现在学习了指针之后,可以尝试使用指针来遍历数组:

void Test()
{
    char arr[5] = {1,2,3,4,5};
    char* p = &arr[0]; //可简写为arr

    for(char i=0;i<5;i++)
    {
        printf("%d\n",*(p+i)); //*(p+i) = p[i]
    }
}

上述代码中&arr[0]代表取数组中第一个元素的地址,可以省略为数组名。