指针学习小结

Y3un9vj

前言

C语言中,指针这一概念一直都是一个重点和难点。学校里教授的指针相关的知识,对于我来说学完基本等于啥也没学,甚至有时面对一些复杂的指针问题,还让我觉得无从下手,对于指针的知识总是感到云里雾里。近期打算把前期没学习完的逆向课程重新拾起,刚好这里涉及到指针的有关知识,感觉了解了指针的本质后,觉得指针也没有那么难,打算在此记录一下有关的知识。最后说一下,滴水海哥的课程真的是深入浅出,对于知识的讲解真的是讲到了本质。

指针的声明、赋值、宽度

忘记以前对指针这一概念的定义,现在开始从新认识指针。指针就是带*的数据类型,每一种数据类型都有自己的宽度,所以指针作为一种数据类型,也有它自己数据宽度。

  • 指针的声明
char* a;
short* b;
int* c;
char** d;
short** e;
int** f;
  • 指针的赋值
a = (char*)100;
b = (short*)100;
c = (int*)100;
d = (char**)200;
e = (short**)200;
f = (int**)200;
  • 利用VC6编写实际代码探索指针的宽度

C代码

char* a;
short* b;
int* c;
char** d;
short** e;
int** f;
a = (char*)100;
b = (short*)100;
c = (int*)100;
d = (char**)200;
e = (short**)200;
f = (int**)200;

printf("%d %d %d %d %d %d", a, b, c, d, e, f);

汇编结果

00401118   mov         dword ptr [ebp-4],64h
0040111F   mov         dword ptr [ebp-8],64h
00401126   mov         dword ptr [ebp-0Ch],64h
0040112D   mov         dword ptr [ebp-10h],0C8h
00401134   mov         dword ptr [ebp-14h],0C8h
0040113B   mov         dword ptr [ebp-18h],0C8h

从上面的实验,可以看出无论有几个*,只要带有*则该变量的宽度就是dword(4个字节)。

总结

  1. 带有*的变量类型的标准写法:变量类型* 变量名
  2. 任何类型都可以带* 加上*以后是新的类型
  3. *可以是任意多个、
  4. *类型的变量赋值时只能使用“完整写法”(变量类型* 变量名 = (变量类型*)值)
  5. *类型的变量宽度永远是4字节、无论类型是什么,无论有几个*

指针的运算

  • 指针进行++、–

C代码

void test(){
  char* a;
  short* b;
  a = (char*)100;
  b = (short*)100;
  a++;
  b++;
  char** c;
  short** d;
  c = (char**)100;
  d = (short**)100;
  c++;
  d++;
  
  printf("%d %d %d %d", a, b, c, d);
}

计算结果

101 102 104 104

结果分析

指针的运算是先将该变量的类型去掉一个*号,然后再加上或者减掉去掉*号后的数据类型的宽度。

char* a = (char*)100; a++;a=101,char*去掉一个*后为char,char的宽度为1字节,所以100+1=101。

char** a = (char**)100; a++; a=104,char**去掉一个*后为char*,宽度为4字节,所以100+4=104。

  • 指针加上/减去一个数

C代码

void test(){
  char* a;
  short* b;
  a = (char*)100;
  b = (short*)100;
  a = a + 4;
  b = b + 4;
  char** c;
  short** d;
  c = (char**)100;
  d = (short**)100;
  c = c + 4;
  d = d + 4;
  
  printf("%d %d %d %d", a, b, c, d);
}

计算结果

104 108 116 116

结果分析

先将该变量的类型去掉一个*号,然后再加上或者减掉去掉*号后的数据类型的宽度乘以数值。

char* a = (char*)100; a = a + 4;a=101,char*去掉一个*后为char,char的宽度为1字节,所以100+1*4=104。

char** a = (char**)100; a = a + 4; a=116,char**去掉一个*后为char*,宽度为4字节,所以100+4*4=116。

  • 指针之间求差值

C代码

void test(){
  char* a;
  short* b;
  char* c;
  a = (char*)100;
  c = (char*)50;
  int x = a - c;
  printf("%d %d %d", a, c, x);
}

计算结果:

100 50 50

结果分析

先计算出运算数的差值,然后再用差值除以去掉一个*数据类型的宽度

如这里a-c=50,100/(char的宽度) = 50

总结

  1. 不带*类型的变量,++或者– 都是假1 或者减1
  2. *类型的变量,可是进行++ 或者 –的操作
  3. *类型的变量,++ 或者 – 新增(减少)的数量是去掉一个*后变量的宽度
  4. *类型的变量可以加、减一个整数,但不能乘或者除
  5. *类型变量与其他整数相加或者相减时: 带*类型变量 + N = 带*类型变量 + N*(去掉一个*后类型的宽度) 带*类型变量 - N = 带*类型变量 - N*(去掉一个*后类型的宽度)
  6. 两个类型相同的带*类型的变量可以进行减法操作
  7. 想减的结果要除以去掉一个*的数据的宽度

指针的比较

C代码

void test(){
	char***** a;
	char***** b;
	a = (char*****)100;
	b = (char*****)50;
	if(a > b){
		printf("1\n");
	}else{
		printf("2\n");
	}
}

计算结果

1

结果分析

带*的变量,如果类型相同,可以做大小的比较

如char*与char*可以,char*与int*不可以,char**与char***不可以

类型转换

  • 这个很好理解,就是将一种数据类型转换为另外一种数据类型。如将int转换为char,char转换为int。
  • 一般来说基本数据类型之间可以相互转换,但不是所有类型之间都能相互转换,如结构体数据类型与基本类型之间进行转换就会报错。

地址符&

  • &是地址符,类型是其后面的类型加一个“*”,任何变量都可以使用&来获取地址,但不能用在常量上。
char a = 10;		
short b = 20;		
int c = 30;		
		
char* pa = (char*)&a;		
short* pb = (short*)&b;		
int* pc = (int*)&c;		
		
简写为:		
char* pa = &a;		
short* pb = &b;		
int* pc = &c;		
char a = 10;		
short b = 20;		
int c = 30;		
		
char* pa = &a;		
short* pb = &b;		
int* pc = &c;		
		
char** ppa = (char**)&pa;		
short** ppb = (short**)&pb;		
int** ppc = (int**)&pc;		
		
简写为:		
char** ppa = &pa;		
short** ppb = &pb;		
int** ppc = &pc;		

指针的值

int x,y;		
int* px;		
int** px2;		
int*** px3;		
		
x = 10;		
px = &x;		
px2 = &px;		
px3 = &px2;		
		
y = *(*(*(px3)));		

总结

  1. *类型的变量,可以通过在其变量前加*来获取其指向内存中存储的值。
  2. 在带类型的变量前面加,类型是其原来的类型减去一个*

指针操作数组

char arr[10];				
				
char* p = &arr[0]; //取数组第一个元素的地址				
char* p2 = arr;    //简写				
char* p = arr;			
			
for(int i=0;i<10;i++)			
{			
	*(p+i) = i*2;		
}			
			
for(int k=0;k<10;k++)			
{			
	printf("%d\n",*(p+k));		
}


short* p = arr;			
			
for(int i=0;i<10;i++)			
{			
	*(p+i) = i*2;		
}			
			
for(int k=0;k<10;k++)			
{			
	printf("%d\n",*(p+k));		
}			


int* p = arr;			
			
for(int i=0;i<10;i++)			
{			
	*(p+i) = i*2;		
}			
			
for(int k=0;k<10;k++)			
{			
	printf("%d\n",*(p+k));		
}			

总结

  1. &arr[0]代表取数组中第一个元素的地址,可以省略为数组名.
  2. *(p+i) = p[i]

练习

  • 模拟实现CE的数字型数据搜索功能
这一堆数据中存储了角色的血值信息,假设血值的类型为int类型,值为100(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
char data[100] = {
  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
};

void FindBlood(){
  for(int i = 0; i < 97; i++){
    int* p = (int*)(data+i);
    if(*p == 0x64){
      printf("%x %d\n", data+i, *p);
    }
  }
}


int main(int argc, char* argv[])
{
  FindBlood();
  return 0;
}

/**
  *思路:
  1、因为要按照所给数据逐个查找,所以只能是char*的数据进行增加(data+i)
  2、因要查找的数据为int型的100,所以只能将所给数据强制转换为int*(int* p = (int*)(data+i))
  */
  • 模拟实现CE的字符型数据搜索功能
这一堆数据中存储了角色的名字信息(WOW),请列出角色名的内存地址.						
            
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,0x57,0x4F,0x57,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						
char data[100] = {
  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,0x57,0x4F,0x57,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
};

char* FindRoleNameAddr(char* pData, char* pRoleName){			
  for(int i = 0; i < 100; i++){		
    if(strcmp(pData+i, pRoleName) == 0){	
      return pData+i;
    }	
  }		
}			
      
int strcmp(char* s1, char* s2){			
  while(*s1 != '\0' && *s2 != '\0'){		
    if(*s1 != *s2){	
      return 1;
    }	
    s1++;	
    s2++;	
  }		
  if(*s1 != *s2){		
    return 1;	
  }		
  return 0;		
}			

int main(int argc, char* argv[])
{
  char a[] = "WOW";
    printf("%x\n", FindRoleNameAddr(data, a));
  return 0;
}

/**
  *思路:
  1、这里的思路和模拟CE搜索数字型数据一样,这里的关键在于strcmp函数的实现
  */
  • 总结
1.char* a = "china"char b[] = "china"的区别
    china”为字符串常量,存储在常量区
    char* a存储的是这个常量字符串的地址,不能修改其内容,所以*(a+1) = "x"为报错
    char b[]存储的是常量字符串“china”的复制,可以修改内容,所以b[1] = "x"不会报错

指针数组

char* arr[5] = {0};		
		
arr[0] = (char*)1;		
arr[1] = (char*)2;		
arr[2] = (char*)3;		
arr[3] = (char*)4;		
arr[4] = (char*)5;		
		
		
char a1 = 'A';		
char a2 = 'B';		
char a3 = 'C';		
char a4 = 'D';		
char a5 = 'E';		
		
char* p1 = &a1;		
char* p2 = &a2;		
char* p3 = &a3;		
char* p4 = &a4;		
char* p5 = &a5;

char* arr[5] = {p1,p2,p3,p4,p5};

char* p1 = "if";			
char* p2 = "for";			
char* p3 = "while";			
char* p4 = "switch";			
			
char* keyword[] = {p1,p2,p3,p4};

结构体指针

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

//创建结构体
Student s;
s.a = 10;
s.b = 20;
s.c = 30;

//声明结构体指针
Student* ps;

//为结构体指针赋值
ps = &s;

//通过指针读取数据
printf("%d\n",ps->a);

//通过指针修改数据
ps->a = 100;

printf("%d\n",ps->a);

练习

  • 题目
查找这些数据中,有几个id=1 level=8的结构体信息。					
					
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,					
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,					
0x00,0x33,0x01,0x00,0x00,0x08,0x00,0x00,0x00,0x00,					
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,					
0x00,0x00,0x64,0x01,0x00,0x00,0x00,0x08,0x00,0x00,					
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,					
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,					
0x00,0x02,0x57,0x4F,0x57,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					
					
结构体定义如下:					
					
typedef struct TagPlayer					
{					
	int id;				
	int level;				
}Player;
  • 实现
typedef struct TagPlayer		
{		
	int id;	
	int level;	
}Player;

char data[100] = {
	0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,					
	0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,					
	0x00,0x33,0x01,0x00,0x00,0x08,0x00,0x00,0x00,0x00,					
	0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,					
	0x00,0x00,0x64,0x01,0x00,0x00,0x00,0x08,0x00,0x00,					
	0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,					
	0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,					
	0x00,0x02,0x57,0x4F,0x57,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
};

void test(){
	for(int i = 0; i < 93; i++){
		Player* player = (Player*)(data+i);
		if(player->id == 1 && player->level == 8){
			printf("%x\n", player);
		}
	}
}


int main(int argc, char* argv[])
{
	test();
	return 0;
}
  • 总结思考
1、从结构体指针,我们可以进一步体会到指针的本质,所有的指针都只是带有*号的一种数据类型而已
2*号前的数据类型int,char,short,struct等都只是来让编译器明白,我们到底要让它从当前地址开始往后取具体多少个字节的数据

多级指针

  • 多级指针就是指针的指针,本质也是指针。
  • *()与[]之间的互换表示

一级指针

int i = 100;		
    
int* p1 = &i;		
    
printf("%d\n",*(p1));		
    
printf("%d\n",*(p1+0));		
    
printf("%d\n",p1[0]);

二级指针

int i = 100;			
      
int* p1 = &i;			
int** p2 = &p1;			
      
printf("%d\n",*(*(p2)));			
      
printf("%d\n",*(*(p2+0)+0));			
      
printf("%d\n",p2[0][0]);

三级指针

int i = 100;			
      
int* p1 = &i;			
int** p2 = &p1;			
int*** p3 = &p2;			
      
printf("%d\n",*(*(*(p3))));			
      
printf("%d\n",*(*(*(p3+0)+0)+0));			
      
printf("%d\n",p3[0][0][0]);

七级指针

int i = 100;					
          
int* p1 = &i;					
int** p2 = &p1;					
int*** p3 = &p2;					
int**** p4 = &p3;					
int***** p5 = &p4;					
int****** p6 = &p5;					
int******* p7 = &p6;					
          
printf("%d\n",*(*(*(*(*(*(*(p7))))))));					
          
printf("%d\n",*(*(*(*(*(*(*(p7+0)+0)+0)+0)+0)+0)+0));					
          
printf("%d\n",p7[0][0][0][0][0][0][0]);

总结

*(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]					
          
*()  []可以相互转换

数组指针

  • 数组指针与指针数组的区别
  1. 指针数组的本质是数组,只不过数组中的元素都是指针
  2. 数组指针的本质是指针,只是这个指针*号前的数据类型为数组
  • 数组指针的声明

int (*px)[2];

理解:

  1. 这里指针的变量为px,类型是int (*)[2]。
  2. 对于int (*)[2]可能不好理解,其实这里可以将其看作是int[2]* px,这样我们就好理解了,其实就是一个带有int[2]类型的指针,和int* px这样的指针其实一样。
  • *px和px的区别

现有如下代码

#include "stdafx.h"

int main(int argc, char* argv[])
{
  int arr[10] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10
  };

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

运行结果

1638176 1638176 1638176

思考分析

问题:
    1.按照我们的以前学习的知识,我们知道对于int* p,p的值为地址,*p为地址所指向的值。可是上面的代码中px,*px的值居然一样,
      都是arr的地址。
分析:
    1.对于px我们可以讲其类型看做是int[2]* px,那么*px就是int[2],int[2]为一个数组,只是一个没有具体名称的长度为2int
      型数组,所以*px就相当于长度为2int型数组的首地址。此时的px*px都是指针。

接着如下代码

#include "stdafx.h"

int main(int argc, char* argv[])
{
  int arr[10] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10
  };

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

运行结果

1638176 1638184 1638180

思考分析

在上面的代码中,我们将px*px分别加了1,但是他们的运行结果却不同,这说明他们的数据宽度是不一样。
对于px+1:相当于加上int[2]的宽度*1
对于*px+1:相当于加上int的宽度*1
所以虽然px*px都是指针,但是它们的数据宽度却是不相同的。
  • int *p[5] 与 int (*p)[5]的区别
[] 的优先级高于*  所以先组合成p[5]数组 再由int *说明 数组存储的类型 == int* p[5]
() 的优先级高于[] 所以*p先组合成指针 再由int[5]说明数组的宽度

多维数组指针

  • 多维数组指针,本质还是指针,只要清楚指针的几个基本特性就好。
  • 二维数组指针:int (*p)[2][2], 三维数组指针int (*p)[2][2][2]
  • 其它类型强制转换为多维数组指针:int (*p)[2][2] = (int (*)[2][2])arr
  • 对于多维数组指针声明和取值过程的理解,例如int (*p)[2][2]
  1. 首先整体可以看做是int[2][2]* p,此时p+2 = 4*2*2*2
  2. *p看做是一维数组指针int[2]* p ,此时*p+1 = 4*2*1
  3. 对于*(*(*(p+1)+1)+1) = 4*2*2*1+4*2*1+4*1
  • 练习
// Test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "stdlib.h"


// 一维数组指针访问一维数组
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));  // 5	
		
	printf("%d\n",*(*(p+1)+4));	 // 10
		
	int (*px)[2] = (int (*)[2])arr;	
		
	printf("%d\n",*(*(px)+4));	// 5
		
	printf("%d\n",*(*(px+1)+4)); // 7	
}		

//一维数组指针访问三维数组
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));   // 5
		
	printf("%d\n",*(*(p+1)+4));	// 0
		
	printf("%d\n",*(*(p+2)+2));	// 11
}		

// 二维数组指针访问一维数组
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]); // 1	
		
	printf("%d %d %d\n",*(*(*(p+1)+1)),p[1][1][0],(*(p+1))[1][0]);	// 4*2*2*1+4*2*1+4*0  7
		
	printf("%d %d %d\n",*(*(*(p+1)+1)+1),p[1][1][1],(*(*(p+1)+1))[1]);	// 4*2*2*1+4*2*1+4*1 8
		
	printf("%d %d %d\n",*(*(*(p+2)+1)+2),p[2][1][2],(*(*(p+2)+1))[2]);  // 4*2*2*2+4*2*1+4*2 13	
}		

//二维数组指针访问二维数组
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]);	// 1
		
	printf("%d %d %d\n",*(*(*(p+1)+1)),p[1][1][0],(*(p+1))[1][0]);	// 4*2*2*1+4*2*1+4*0 7
		
	printf("%d %d %d\n",*(*(*(p+1)+1)+1),p[1][1][1],(*(*(p+1)+1))[1]);	// 4*2*2*1+4*2*1+4*1 8
		
	printf("%d %d %d\n",*(*(*(p+3)+2)+2),p[3][2][2],(*(*(p+3)+2))[2]);	// 4*2*2*3+4*2*2+4*2 24
}		

/**
 *	
 *	char数组内容如下:									
 *  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					

 *  不运行说出下面的结果:			指针定义如下:		

 *  *(*(px+0)+0)			    int (*px)[2]; 		

 *  *(*(px+1)+0)			    int (*py)[2][3]; 		

 *  *(*(px+2)+3)			    char (*pz)[2];		

 *  *(*(*(py+1)+2)+3)			char (*pk)[2][3];		

 *  *(*(pz+2)+3)
 */

char data[] = {
	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
};

int main(int argc, char* argv[])
{
	//int (*px)[2]; 
	
	//int (*py)[2][3]; 	
		
	//char (*pz)[2];	
		
	char (*pk)[2][3];	

	pk = (char (*)[2][3])data;

	// printf("%x\n", *(*(px+0)+0));	// 0x03020100
	
	// printf("%x\n", *(*(px+1)+0));    // 0x20000907
		
	// printf("%x\n", *(*(px+2)+3));	// 0x00001100
		
	// printf("%x\n", *(*(*(py+1)+2)+3));	// 0x00000001
		
	//printf("%x\n", *(*(pz+2)+3));   // 0x07	
		
	printf("%x\n", *(*(*(pk+2)+3)+4));	// 0x0E

	system("pause");
	return 0;
}

函数指针

  • 函数指针的声明: 返回类型 (*函数名)(参数表) 如:int (*pFun)(int,int);
  • 函数指针的赋值 pFun = (int (*)(int,int))10; 或者pFun = 函数名。
  • 函数指针与其它指针的异同
    1. 与其它指针一样数据宽度都是4字节
    2. 可以进行比较
    3. 不能进行++,–,+-整数,指针之间不能进行相加相减 原因:因每一个函数的函数体中的内容可能不一样,所以它们的数据大小不确定,这就导致不能进行运算。
  • 函数指针的使用方式 与函数的使用一致
  • 练习:隐藏代码到数据区 编写一个源代码:
#include "stdafx.h"
#include "stdlib.h"

int Function(int x, int y){
    return x+y;
}

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

	// fpx = (int (__cdecl *)(int, int))10;
    fpx = Function;
	printf("%d\n", fpx(2,3));
	system("pause");
	return 0;
}

提取出Function函数的汇编代码中提取出二进制代码: Function的汇编:

00401100 55                   push        ebp
00401101 8B EC                mov         ebp,esp
00401103 83 EC 40             sub         esp,40h
00401106 53                   push        ebx
00401107 56                   push        esi
00401108 57                   push        edi
00401109 8D 7D C0             lea         edi,[ebp-40h]
0040110C B9 10 00 00 00       mov         ecx,10h
00401111 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
00401116 F3 AB                rep stos    dword ptr [edi]
8:        return x+y;
00401118 8B 45 08             mov         eax,dword ptr [ebp+8]
0040111B 03 45 0C             add         eax,dword ptr [ebp+0Ch]
9:    }
0040111E 5F                   pop         edi
0040111F 5E                   pop         esi
00401120 5B                   pop         ebx
00401121 8B E5                mov         esp,ebp
00401123 5D                   pop         ebp
00401124 C3                   ret

Function的二进制:

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

将Function的二进制代码放到一个数组中,目的是将函数代码从代码区放入数据区然后通过函数指针对函数进行操作

#include "stdafx.h"
#include "stdlib.h"

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 (*fpx)(int, int);

	fpx = (int (__cdecl *)(int, int))&code;
	printf("%d\n", fpx(2,3));
	system("pause");
	return 0;
}

通过上面的练习,我们初步可以了解到函数指针的作用。函数指针其中一个应用可以对我们的软件进行加壳处理,防别人逆向。 从上面的练习,我们可以再次体会到其实数据区与代码区的数据没什么区别,都是一些二进制数据,重要的是编译器对这些数据的理解。