指针的本质是什么?指针指向什么跟它本身是什么没有任何关系,比如结构体指针并不是非得指向一个结构体,数组指针也不是非得指向指定长度或者类型的数组,指针就是指针,我们想让它指啥就指啥,指针只是一个用来操作数据的工具。

指针的本质海哥概括为以下六条:
1、它是一个类型,是前面带 * 的,带几个都行
2、它的宽度都是 4
3、它可以做 ++\-- 的运算,到底加几减几看砍掉一个 * 后的宽度
4、与一个整数相加或相减
5、可以比较大小
6、两个指针可以相减,相减后的结果也要除以砍掉一个 * 后的宽度
是曰:除了以上以外,其余全是扯。

数组指针

一维、二维、三维数组指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include "stdafx.h"

char code[] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x10,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x70,0x00,
0x10,0x20,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};

int main(int argc,char* argv[]){
//一维数组指针
char (*px)[5];
px = (char (*)[5])code;
printf("%x\n",*(*(px+4)+3)); //4*(5*1)+3*1=20+3=23 0x47

int (*pxx)[5];
pxx = (int (*)[5])code;
printf("%x\n",*(*(pxx+2)+2)); //2*(5*4)+2*4=40+8=48 过了48个,但是int是取4个字节,所以地址从低到高分别是0xA0 0x00 0x00 0x00,但是因为在电脑内都是小端存储,所以打印出来就是0x00 0x00 0x00 0xA0,前面都是0,总的表现出来也就是A0

//二维数组指针
char (*py)[2][3];
py = (char (*)[2][3])code;
//一维数组取出来里面的值需要俩*,二维就需要三个
printf("%x\n",*(*(*(py+2)+3)+4)); //2*(2*3*1)+3*(3*1)+4*1=12+9+4=25 0x0E
printf("%x\n",*(*(*(py+3)+2)+5)); //3*(2*3*1)+2*(3*1)+5*1=18+6+5=29 0x11

int (*pyy)[2][3];
pyy = (int (*)[2][3])code;
printf("%x\n",*(*(*(pyy+2)+2)+2)); //2*(2*3*4)+2*(3*4)+2*4=48+24+8=80 过了80个,int取4字节,地址从低到高0x00 0x00 0x00 0x64,小端序存储,最终表示为64000000

//三维数组指针
char (*pz)[2][3][2];
pz = (char (*)[2][3][2])code;
printf("%x\n",*(*(*(*(pz+2)+2)+2)+2)); //2*(2*3*2*1)+2*(3*2*1)+2*(2*1)+2*1=24+12+4+2=42 0x64
return 0;
}

n 维数组指针访问 m 维数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//一维数组指针访问一维数组		
void Function_1_1() {
int arr[5] = {1,2,3,4,5};

int (*p)[5] = (int (*)[5])arr;
printf("%d\n",*(*(p)+4));

int (*px)[2] = (int (*)[2])arr;
printf("%d\n",*(*(px+1)+2));
printf("%d\n",*(*(px+2)+0));
}

//一维数组指针访问二维数组
void Function_1_2() {
int arr[2][5] = {
{1,2,3,4,5},
{6,7,8,9,10}
};
int (*p)[5] = (int (*)[5])arr;
printf("%d\n",*(*(p)+4));
printf("%d\n",*(*(p+1)+4));

int (*px)[2] = (int (*)[2])arr;
printf("%d\n",*(*(px)+4));
printf("%d\n",*(*(px+1)+4));
}

//一维数组指针访问三维数组
void Function_1_3() {
int arr[3][3][2] = {
{{1,2},{3,4},{5}},
{{6,7},{8},{9,10}},
{{11},{12,13},{14,15}}
};
int (*p)[5] = (int (*)[5])arr;
printf("%d\n",*(*(p)+4));
printf("%d\n",*(*(p+1)+4));
printf("%d\n",*(*(p+2)+2));
}

//二维数组指针访问一维数组
void Function_2_1() {
int arr[15] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};

int (*p)[2][2] = (int (*)[2][2])arr;
printf("%d %d %d\n",*(*(*(p))),p[0][0][0],(*(p))[0][0]);
printf("%d %d %d\n",*(*(*(p+1)+1)),p[1][1][0],(*(p+1))[1][0]);
printf("%d %d %d\n",*(*(*(p+1)+1)+1),p[1][1][1],(*(*(p+1)+1))[1]);
printf("%d %d %d\n",*(*(*(p+2)+1)+2),p[2][1][2],(*(*(p+2)+1))[2]);
}

//二维数组指针访问二维数组
void Function_2_2() {
int arr[2][15] = {
{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
{21,22,23,24,25,26,27,28,29,30,31,32,33,34,35}
};

int (*p)[2][2] = (int (*)[2][2])arr;
printf("%d %d %d\n",*(*(*(p))),p[0][0][0],(*(p))[0][0]);
printf("%d %d %d\n",*(*(*(p+1)+1)),p[1][1][0],(*(p+1))[1][0]);
printf("%d %d %d\n",*(*(*(p+1)+1)+1),p[1][1][1],(*(*(p+1)+1))[1]);
printf("%d %d %d\n",*(*(*(p+3)+2)+2),p[3][2][2],(*(*(p+3)+2))[2]);
}

函数指针

  • 函数指针的声明
    返回类型(*函数名)(参数表),如 int (*pFun)(int,int)

  • 函数指针的属性探测

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int main(int argc, char* argv[]) {
    int (*pFunc)(int, int);
    //宽度探测
    printf("%d\n", sizeof(pFunc)); //4
    pFunc = (int (__cdecl *)(int,int))10; //默认__cdecl,删掉这么写也可以(int (*)(int,int))
    //++-- +-整数 相减
    pFunc++;
    printf("%d\n", pFunc); //函数的宽度是不确定的(多写几句内存大小也会跟着变),所以这些都是编译不过去的
    //大小比较可以做,就不演示了
    }
  • 函数指针的赋值
    pFun = (int (*)(int,int))10 或者 pFun = 函数名。
    虽然编译的时候可以通过强制转型编译通过,但是运行的时候就会挂了,因此如果想要让这个函数指针有意义,那么就必须给它赋值一个格式跟它一样的函数,包括返回值和参数的个数与类型,比如函数指针声明的返回值是 void,那赋值的函数也得是 void,如果包含两个 int 参数,那么赋值的函数也得是有俩 int 参数的

  • 函数指针的使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int Function(int x,int y) {
    return x + y;
    }
    int main(int argc,char* argv[]) {
    int (*pFun)(int,int);
    pFun = Function;
    int sum = pFun(1,2);
    printf("%d",sum); //1+2=3
    return 0;
    }

    与函数的使用方式是相同的。那既然都一样这东西还有啥用呢?课上简单提了一嘴,调用dll时要用到,以我薄弱的功底此前没有接触过此方面,等后面学到再说吧,还有就是隐藏函数代码到数据区时也会用,下面会讲到。

  • 隐藏代码到数据区
    代码和数据本质上是没有区别的,都是一堆存储在内存中的二进制数,使用硬编码展示,所以如果知道了硬编码,也就可以还原出来函数,VC 就是这么做的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    //Function的硬编码
    unsigned char code[] = {
    0x55,
    0x8B, 0xEC,
    0x83, 0xEC, 0x40,
    0x53,
    0x56,
    0x57,
    0x8D, 0x7D, 0xC0,
    0xB9, 0x10, 0x00, 0x00, 0x00,
    0xB8, 0xCC, 0xCC, 0xCC, 0xCC,
    0xF3, 0xAB,
    0x8B, 0x45, 0x08,
    0x03, 0x45, 0x0C,
    0x5F,
    0x5E,
    0x5B,
    0x8B, 0xE5,
    0x5D,
    0xC3
    };
    int main(int argc, char* argv[]) {
    int (*Fun)(int,int);
    Fun = (int (*)(int,int))&code;
    int x = p(2,3);
    printf("%d\n",x); //5
    return 0;
    }

    这东西有啥用?如果你想写一个壳来保护你的程序,你去把你自己写的所有的函数都抠出来,放到一个你自己定义的数组里(数据区),然后再对这个数组做做手脚,再把原来存储这些函数的地方全部变成 0,就可以给想要逆你程序的人上难度!

课后作业

  1. 下列说法是否正确?为什么?

    1
    2
    3
    4
    指针的指针:就是指向指针的指针	
    结构指针:就是指向结构的指针
    数组指针:就是指向数组的指针
    函数指针:就是指向函数的指针

    错!指针就是指针,想让它是啥就是啥,想让它指啥就指啥

  2. 将一个函数存储到数据区,通过指针进行访问
    写一个 x*y 的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    // 222.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"

    /*int Function(int x, int y) {
    return x*y;
    }*/

    unsigned char code[] = {
    0x55,
    0x8B, 0xEC,
    0x83, 0xEC, 0x40,
    0x53,
    0x56,
    0x57,
    0x8D, 0x7D, 0xC0,
    0xB9, 0x10, 0x00, 0x00, 0x00,
    0xB8, 0xCC, 0xCC, 0xCC, 0xCC,
    0xF3, 0xAB,
    0x8B, 0x45, 0x08,
    0x0F, 0xAF, 0x45, 0x0C,
    0x5F,
    0x5E,
    0x5B,
    0x8B, 0xE5,
    0x5D,
    0xC3
    };

    int main(int argc, char* argv[]) {
    int (*pFun)(int,int);
    pFun = (int (*)(int,int))&code;
    int sum = pFun(2,3);
    printf("%d",sum);
    return 0;
    }

    运行后输出是 6

  3. char 数组内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
    0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
    0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
    0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
    0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
    0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
    0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00,
    0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
    0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00

    指针定义如下:

    1
    2
    3
    4
    int (*px)[2];
    int (*py)[2][3];
    char (*pz)[2];
    char (*pk)[2][3];

    不运行说出下面结果:

    • *(*(px+0)+0):0x00
    • *(*(px+1)+0):1*(2*4)+0=8 往后数 8 个,int 取 4 字节 0x07 0x09 0x00 0x20,小端序 0x20000907
    • *(*(px+2)+3):2*(2*4)+3*4=28 往后数 28 个,int 取 4 字节 0x00 0x11 0x00 0x00,小端序 0x1100
    • *(*(*(py+1)+2)+3):1*(2*3*4)+2*(3*4)+3*4=60 往后数 60 个,int 取 4 字节 0x01 0x00 0x00 0x00,小端序 0x01
    • *(*(pz+2)+3):2*(2*1)+3*1=7 往后数 7 个 0x07
    • *(*(*(pk+2)+3)+4):2*(2*3*1)+3*(3*1)+4*1=25 往后数 25 个 0x0E