指针初窥

指针初窥

0x01 介绍

本篇文章将介绍C语言中一种带 * 的类型——指针,指针的强大不由分说,这里我将简单介绍其一些特性和用法,如有不对的地方还望指正。

0x02 环境

Visual C++ 6.0

0x03 分析

首先,简单说一下指针类型(以下称带 * 类型)是如何声明的:
带有 * 的变量类型的标准写法:变量类型 * 变量名,其中 * 可以是任意多个,任何类型都可以带 *,加上 * 以后是新的类型。如下所示:

char* x;    

short** y;    

int*** z;    

float***** f;    

double****** d;    

Student******* st;    

然后,我们看下带 * 类型是怎么赋值的:
通常,我们对char、short、int类型的变量赋值一般如下所示:

char x;
short y;
int z;

x = 1;
y = 2;
z = 3;

其实,以上是一种简化的写法,完整的写法如下所示:

char x;    
short y;    
int z;    

x = (char)1;    
y = (short)2;    
z = (int)3;    

同样,带 * 类型的变量赋值如下所示:

char* x;    
short* y;    
int* z;    

x = (char*)1;    
y = (short*)2;    
z = (int*)3;    

需要注意的是,带 * 类型的变量赋值的写法不能用简化的写法,只能用完整的写法。
下面开始研究带 * 类型的宽度,先编写一段简单的测试代码,如下所示:

#include "stdafx.h"


void Test()
{
    char* x;
    short* y;
    int* z;

    x = (char*)1;
    y = (short*)2;
    z = (int*)3;
}

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

    return 0;        
}

运行之后查看反汇编,可以看到不论是char、short、还是int*,在反汇编的都是占用4字节,如下所示:

00401010   push        ebp
00401011   mov         ebp,esp
00401013   sub         esp,4Ch
00401016   push        ebx
00401017   push        esi
00401018   push        edi
00401019   lea         edi,[ebp-4Ch]
0040101C   mov         ecx,13h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos    dword ptr [edi]
00401028   mov         dword ptr [ebp-4],1    //x = (char*)1
0040102F   mov         dword ptr [ebp-8],2    //y = (short*)2
00401036   mov         dword ptr [ebp-0Ch],3  //z = (int*)3
0040103D   pop         edi
0040103E   pop         esi
0040103F   pop         ebx
00401040   mov         esp,ebp
00401042   pop         ebp

然后我们再定义一个struct类型,代码如下所示:

#include "stdafx.h"


struct Student
{
    int a;
    int b;
    int c;
};

void Test()
{
    char* x;
    short* y;
    int* z;
    Student* s;

    x = (char*)1;
    y = (short*)2;
    z = (int*)3;
    s = (Student*)4;
}

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

    return 0;        
}

查看反汇编,可以发现struct* 在反汇编中还是占用4字节,如下所示:

00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,50h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-50h]
0040102C   mov         ecx,14h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
00401038   mov         dword ptr [ebp-4],1
0040103F   mov         dword ptr [ebp-8],2
00401046   mov         dword ptr [ebp-0Ch],3
0040104D   mov         dword ptr [ebp-10h],4   //s = (Student*)4
00401054   pop         edi
00401055   pop         esi
00401056   pop         ebx
00401057   mov         esp,ebp
00401059   pop         ebp

现在,将代码修改一下,增加 * 的数量来观察其宽度是否发生变化,如下所示:

#include "stdafx.h"


void Test()
{
    char** x;
    short** y;
    int** z;

    x = (char**)1;
    y = (short**)2;
    z = (int**)3;
}

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

    return 0;        
}

观察反汇编,发现其还是占用4个字节,如下所示:

00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,4Ch
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-4Ch]
0040102C   mov         ecx,13h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
00401038   mov         dword ptr [ebp-4],1
0040103F   mov         dword ptr [ebp-8],2
00401046   mov         dword ptr [ebp-0Ch],3
0040104D   pop         edi
0040104E   pop         esi
0040104F   pop         ebx
00401050   mov         esp,ebp
00401052   pop         ebp

这里可以在源代码中增加 * 的数量继续进行测试(本文就不再进行测试),最后可以发现带 * 类型的变量宽度永远是4字节、无论 * 前面的类型是什么,无论有几个 *。

下面继续修改源代码,如下所示:

#include "stdafx.h"


void Test()
{
    char* a ;        
    short* b ;        
    int* c ;        

    a = (char*)100;        
    b = (short*)100;        
    c = (int*)100;        

    a++;        
    b++;        
    c++;        

    printf("%d %d %d",a,b,c);        
}

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

    return 0;        
}

这里直接运行一下查看结果,如下所示:

++

可以看到结果为101、102、104,下面我们将源代码的类型改为两个 * ,如下所示:

#include "stdafx.h"


void Test()
{
    char** a ;        
    short** b ;        
    int** c ;        

    a = (char**)100;        
    b = (short**)100;        
    c = (int**)100;        

    a++;        
    b++;        
    c++;        

    printf("%d %d %d",a,b,c);        
}

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

    return 0;        
}

查看运行结果,如下所示:

++_2

可以看到结果均为104。这里可以继续再在源代码中增加 * 的数量进行测试(这里不再测试),可以得到结果仍均为104。同样,带 * 类型的变量也可以进行–操作。
总结一下,带 * 类型的变量,++ 或者 – 新增(或者减少)的数量是去掉一个 * 后变量的宽度。
继续修改源代码,将带 * 变量加上(也可以减去)一个整数,如下所示:

#include "stdafx.h"


void Test()
{
    char* a ;        
    short* b ;        
    int* c ;        

    a = (char*)100;        
    b = (short*)100;        
    c = (int*)100;        

    a = a + 5;        
    b = b + 5;        
    c = c + 5;        

    printf("%d %d %d",a,b,c);        
}

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

    return 0;        
}

直接运行查看结果,如下所示:

+n

可以看到结果为105、110、120。接着继续在源代码中增加*的数量,如下所示:

#include "stdafx.h"


void Test()
{
    char*** a ;        
    short*** b ;        
    int*** c ;        

    a = (char***)100;        
    b = (short***)100;        
    c = (int***)100;        

    a = a + 5;        
    b = b + 5;        
    c = c + 5;        

    printf("%d %d %d",a,b,c);        
}

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

    return 0;        
}

查看运行结果,如下所示:

+n_2

可以看到结果均为120,总结一下,带 * 类型变量与其他整数N相加或者相减时,带 * 类型变量 +/- N = 带 * 类型变量 +/- N* (去掉一个 * 后类型的宽度),但是带 * 类型的变量不能乘或者除一个整数(编译器编译不通过)。
继续修改源代码,如下所示:

#include "stdafx.h"


void Test()
{
    char* a ;    
    char* b ;    

    a = (char*)200;    
    b = (char*)100;    

    int x = a - b;    

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

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

    return 0;        
}

查看结果,如下所示:

sub

可以看到结果为100,接着修改源代码,增加 * 的数量,如下所示:

#include "stdafx.h"


void Test()
{
    char** a ;    
    char** b ;    

    a = (char**)200;    
    b = (char**)100;    

    int x = a - b;    

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

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

    return 0;        
}

查看结果,如下所示:

sub_2

可以看到结果为25。总结一下,两个带 * 类型的变量可以进行相减(相加的话编译器会编译不通过)操作,相减的结果要除以去掉一个 * 的数据的宽度。这里需要注意的是,这两个带 * 类型的变量必须是类型完全相同的,否则编译器编译不会通过。
下面继续修改源代码,如下所示:

#include "stdafx.h"


void Test()
{
    char**** a ;            
    char**** b ;            

    a = (char****)200;            
    b = (char****)100;            


    if(a>b)            
    {            
        printf("1");        
    }            
    else            
    {            
        printf("2");        
    }            
}

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

    return 0;        
}

直接编译运行,查看结果,如下所示:

cmp

可以看到结果为1,说明带 * 的变量,如果类型相同,是可以进行大小比较的。

0x04 总结

  1. 指针,是一种带 * 的类型
  2. 任何类型前面都可以加上 * ,加上 * 就是新的类型
  3. 变量类型的声明是:变量类型* 变量名
  4. 赋值时只能使用完整的写法
  5. 其宽度永远是4字节、无论类型是什么,无论有几个 *
  6. 可以进行++或者–操作,++ 或者 – 新增(或者减少)的数量是去掉一个 * 后变量的宽度
  7. 带 * 类型的变量可以加、减一个整数,但不能乘或者除
  8. 带 * 类型变量与其他整数N相加或者相减时,带 * 类型变量 +/- N = 带 * 类型变量 +/- N* (去掉一个 * 后类型的宽度)
  9. 两个类型相同的带 * 类型的变量可以进行减法操作,相减的结果要除以去掉一个 * 的数据的宽度
  10. 带 * 的变量,如果类型相同,可以做大小的比较。