C语言指针相关学习

发布时间 2023-07-03 20:03:56作者: Qsons

C语言指针相关学习

一、指针书写规范

  • 任何类型不建议写成 类型 变量 ;建议写成类型* 变量*.
  • 任何数据类型后面都可以加上*.

举例:

  1. 建议写成int* x,不建议写成int *x.
  2. 写成Struct* x,而不是Struct *x.
#include "stdafx.h"


int main(int argc,char* argv[]){
    //规范写法
    int* x;

    //不规范写法
    int *x;

    //可以写无数个*
    int************ x;

    return 0;

总结

  1. 带有*的变量类型的标准写法: 变量类型** 变量名
  2. 任何类型都可以带上* 加上*以后是新的类型
  3. *可以是任意多个

二、带*类型的赋值

  • 编译器版本Vc6
  • Debug
#include "stdafx.h"

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

    x = (char*)1;//必须用完整写法对变量进行赋值。
    y = (short*)2;
    z = (int*)3;
/*
这里如果给char*类型的变量赋值,必须指明赋值的*类型。
不能直接x = 1;
编译器会把1当做int *类型来看待.

*/

}


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

    return 0;
}
  • 当声明变量为多个*时,给对应变量赋值时也要注意类型转换

例如char** **x,当我们给这个x赋值时,我们必须指定这个值的类型为****.

三、带*类型的宽度

#include "stdafx.h"

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

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


}

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

	return 0;
}

//汇编代码
Test:
0040B960 55                   push        ebp
0040B961 8B EC                mov         ebp,esp
0040B963 83 EC 4C             sub         esp,4Ch
0040B966 53                   push        ebx
0040B967 56                   push        esi
0040B968 57                   push        edi
0040B969 8D 7D B4             lea         edi,[ebp-4Ch]
0040B96C B9 13 00 00 00       mov         ecx,13h
0040B971 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
0040B976 F3 AB                rep stos    dword ptr [edi]
0040B978 C6 45 FC 01          mov         byte ptr [ebp-4],1
0040B97C 66 C7 45 F8 02 00    mov         word ptr [ebp-8],offset Test+20h (0040b980)
0040B982 C7 45 F4 03 00 00 00 mov         dword ptr [ebp-0Ch],3
0040B989 5F                   pop         edi
0040B98A 5E                   pop         esi
0040B98B 5B                   pop         ebx
0040B98C 8B E5                mov         esp,ebp
0040B98E 5D                   pop         ebp
0040B98F C3                   ret


我们知道堆栈在Vc中如果不创建局部变量的话,默认分配0x40H字节空间,当我们创建char,short,int时。编译器一样各分配4个字节空间.只是在给局部变量赋值的时候,根据值的大小来指定数据宽度。那我们试着想一下,带*类型的变量在汇编中,宽度是如何分配的呢?

代码如下:

#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;
}

汇编代码如下:

0040B960 55                   push        ebp
0040B961 8B EC                mov         ebp,esp
0040B963 83 EC 4C             sub         esp,4Ch
0040B966 53                   push        ebx
0040B967 56                   push        esi
0040B968 57                   push        edi
0040B969 8D 7D B4             lea         edi,[ebp-4Ch]
0040B96C B9 13 00 00 00       mov         ecx,13h
0040B971 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
0040B976 F3 AB                rep stos    dword ptr [edi]
0040B978 C7 45 FC 01 00 00 00 mov         dword ptr [ebp-4],1
0040B97F C7 45 F8 02 00 00 00 mov         dword ptr [ebp-8],2
0040B986 C7 45 F4 03 00 00 00 mov         dword ptr [ebp-0Ch],3
0040B98D 5F                   pop         edi
0040B98E 5E                   pop         esi
0040B98F 5B                   pop         ebx
0040B990 8B E5                mov         esp,ebp
0040B992 5D                   pop         ebp
0040B993 C3                   ret

初始空间是40H,这里分配了4CH字节空间,也就是三个带*类型的局部变量都各分配了4个字节。但是在赋值的时候,我们发现不管 *类型前面的数据类型是char、short、int也好,指定的数据宽度都是dword,即4字节!!!

那如果我创建一个结构体*类型的变量呢?数据宽度还是dword吗?

#include "stdafx.h"

struct Student
{
	int a;
	int x;
	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;
}
Test:
0040B960 55                   push        ebp
0040B961 8B EC                mov         ebp,esp
0040B963 83 EC 50             sub         esp,50h
0040B966 53                   push        ebx
0040B967 56                   push        esi
0040B968 57                   push        edi
0040B969 8D 7D B0             lea         edi,[ebp-50h]
0040B96C B9 14 00 00 00       mov         ecx,14h
0040B971 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
0040B976 F3 AB                rep stos    dword ptr [edi]
0040B978 C7 45 FC 01 00 00 00 mov         dword ptr [ebp-4],1
0040B97F C7 45 F8 02 00 00 00 mov         dword ptr [ebp-8],2
0040B986 C7 45 F4 03 00 00 00 mov         dword ptr [ebp-0Ch],3
0040B98D C7 45 F0 04 00 00 00 mov         dword ptr [ebp-10h],4
0040B994 5F                   pop         edi
0040B995 5E                   pop         esi
0040B996 5B                   pop         ebx
0040B997 8B E5                mov         esp,ebp
0040B999 5D                   pop         ebp
0040B99A C3                   ret

可以看到,我们给结构体对象s,赋值为4,但是在汇编中,其指定的数据宽度仍然是dword,即4字节.

如果我们给的类型是二个*呢?

#include "stdafx.h"

struct Student
{
	int a;
	int x;
	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;
}
//汇编代码
Test:
0040B960 55                   push        ebp
0040B961 8B EC                mov         ebp,esp
0040B963 83 EC 50             sub         esp,50h
0040B966 53                   push        ebx
0040B967 56                   push        esi
0040B968 57                   push        edi
0040B969 8D 7D B0             lea         edi,[ebp-50h]
0040B96C B9 14 00 00 00       mov         ecx,14h
0040B971 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
0040B976 F3 AB                rep stos    dword ptr [edi]
0040B978 C7 45 FC 01 00 00 00 mov         dword ptr [ebp-4],1
0040B97F C7 45 F8 02 00 00 00 mov         dword ptr [ebp-8],2
0040B986 C7 45 F4 03 00 00 00 mov         dword ptr [ebp-0Ch],3
0040B98D C7 45 F0 04 00 00 00 mov         dword ptr [ebp-10h],4
0040B994 5F                   pop         edi
0040B995 5E                   pop         esi
0040B996 5B                   pop         ebx
0040B997 8B E5                mov         esp,ebp
0040B999 5D                   pop         ebp
0040B99A C3                   ret

尽管我们指定的类型是二个*,但是在往局部变量里面赋值的时候,我们可以观察到,其指定的数据宽度仍然是dword,即4字节.

结论:不管我数据类型是char,short,int,Struct也好,只要我是*类型的变量,在数据宽度的赋值时,统一用dword.

  • 带**类型的变量赋值时只能使用"完整写法",即char* * a.
  • 带**类型的变量宽度永远是4字节、无论类型是什么,无论有几个.*(仅限32位)

四、带*类型的++和--

C代码如下:

#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);
    //打印结果101,102,104
}

int main(int argc,char* argv[]){
	Test();
	return 0;
}
#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);//打印结果104,104,104
}

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

事实上,带*类型的变量在进行自增(++),自减(--)是有公式的。

公式为:带*类型的变量,++ 或者 -- 新增(减少)的数量是去掉一个 *后变量的宽度.

所以在第一个程序中

  • 变量都为带一个 * 。去掉一个 * 后变量的宽度分别为char(1字节),short(2字节),int(4字节)。
  • 对应最后的输出结果就是100+1,100+2,100+4

在第二个程序中

  • 变量都为带二个 *.去掉一个 * 后,都带一个 * .*刚刚讲过带 变量的数据宽度在32位操作系统中,永远是dword即4字节
  • 所以对应的最后输出结果就是100+4,100+4,100+4.

在带 * 的数据类型的变量中第一级对应的是charint自己本身的数据宽度,第二层以上都是对应的本机字长

总结

  1. 不带 * 类型的变量,++或者--, 都是加1 或者 减 1
  2. 带 * 类型的变量,可以进行++ 或者 --的操作
  3. 带 * 类型的变量, ++ 或者 -- 新增(减少)的数量是去掉一个*后变量的宽度

五、带*类型的加上或者减去一个整数

代码如下:

#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);//打印结果为105,110,120
}

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

输出的结果为105,110,120。为什么呢?

在带*的数据类型中,与其他整数相加或者相减时:

  • 类型变量 + N = 带 * 类型变量 + N * (去掉一个后类型的宽度)
  • 带*类型变量 - N = 带 * 类型变量 - N *(去掉一个 * 后类型的宽度)

实际上在与其他整数相加或者相减时,带 * 类型的值为加的值(减的值) 乘以 本身的宽度。

所以在该程序中

  • a为char * 类型
    1. 去掉一个 * 后类型的宽度为char,即1字节,100 + 5 * 1
    2. 最后的输出的结果为105
  • b为short * 类型
    1. 去掉一个 * 后类型的宽度为short类型,即word,2个字节
    2. 所以最后的输出结果应该为100 + 2 * 5 = 110
  • c为int * 类型
    1. 去掉一个 * 后类型的宽度为int类型,即dword,4个字节
    2. 所以最后的输出结果应该为100 + 4 * 5 = 120

我们再来试试,多个带 * 的数据类型

C代码如下:

#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);//打印结果均为120
}

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

减法情况如下:

#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);//打印结果均为80
}

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

在该程序中,a,b,c都是带2个 * 以上的数据类型,在减去5时

  1. a去掉一个 * 后为4个 *,所以最终的结果为100 - 4 * 5 = 80
  2. b去掉一个 * 后为4个 *,所以最终的结果为100 - 4 * 5 = 80
  3. c 去掉一个 * 后为4个*, 所以最终的结果为100 - 4 * 5 = 80

总结:

  1. 带*类型的变量可以加、减一个整数,但不能乘或者除
  2. 带*类型变量与其他整数相加或者相减时:
    1. 类型变量 + N = 带 * 类型变量 + N(去掉一个 * 后类型的宽度)
    2. 类型变量 - N = 带 * 类型变量 - N(去掉一个 * 后类型的宽度)

六、带*类型的乘以或者除以一个整数

总结:

  • 带*类型变量不能乘或者除

七、带*类型的求差值

C语言代码如下:

#include "stdafx.h"


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

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

	int x = a - b;
	printf("%d\n",x);//打印的结果为100
}

int main(int argc,char* argv[]){
	Test();
	return 0;
}
#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;
}
#无法通过编译,因为其相减的 * 类型不同。
#include "stdafx.h"


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

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

	int x = a - b;
	printf("%d\n",x);
}

int main(int argc,char* argv[]){
	Test();
	return 0;
}
#无法编译,int*和char*类型不同
#include "stdafx.h"


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

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

	int x = a - b;
	printf("%d\n",x);//打印结果为25
}

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

第一个程序中

  • 在进行带*类型的变量减法操作时,要满足以下两点
    1. 二个类型相同的带*类型的变量才可以进行减法操作
    2. 想减的结果要除以去掉一个*后的数据的宽度
  • 所以在第一个程序中,b去掉一个*后的数据宽度为char,即宽度为1字节,所以最终的答案为200-100

第二个程序中

  • a和b都是char**类型的变量,在都去掉一个 * 类型之后的宽度仍然为char*,之前讲过带 *类型的任何数据变量,都为dword,即4个字节。所以最终的答案为:(200 - 100) / 4

总结:

  1. 两个类型相同的带*类型的变量可以进行减法操作.
  2. 想减的结果要除以去掉一个*的数据的宽度

八、带*类型的做比较

C语言代码如下:

#include "stdafx.h"


void Test()
{
	char**** a;
	char**** b;
	a = (char****)200;
	b = (char****)100;
	if(a>b)
	{
		printf("1");//打印结果为1
	}
	else
	{
		printf("2");
	}
}

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

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

九、类型转换

代码如下:

#include "stdafx.h"


void Test()
{
	char* x;
	int* y;
	x = (char*)10;
	y = x;

}

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

编译器报错如下:

  • error C2440: '=' : cannot convert from 'char *' to 'int *'
    Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

意思是说我们不能将char* 类型的值直接复制给int* 类型的值.所以我们需要强制类型转换

  1. y = (int*)x;

那么int* 类型的变量可以转换给结构体* 类型的变量么?

答案是可以的

#include "stdafx.h"


struct Student
{
	int age;
	int id;
	int level;

};

void Test()
{
	int* x;
	Student* y;
	x = (int*)0xFFFF;
	y = (Student*)x;

}

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

虽然结构体不能直接转int或者char。但是在带*类型的变量中,他们在内存中所占的字节都为dword,即4字节。

所以当我将一个int或者其他带 * 类型的变量赋值给结构体带* 类型的变量,我们可以通过类型转换来进行赋值。

我们再来看一个案例

#include "stdafx.h"


void Test()
{
	char* x;
	char** y;

	x = (char*)10;
	y = x;

}

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

编译之后报错,我们刚刚讲过带*类型的变量所占字节不都是为4字节吗?那么我这里的x为char * 类型,y为char ** 类型。

按理来说,直接进行转换就可以了,不过我们这里出现了报错。我们现在试试将x进行类型转换。

#include "stdafx.h"


void Test()
{
	char* x;
	char** y;

	x = (char*)10;
	y = (char**)x;

}

int main(int argc,char* argv[])
{
	Test();
	return 0;
}
#编译器正常编译执行

十、&符号的使用

&符号又名取地址符,类型是其后面的类型加一个"*",任何变量都可以使用&来获取地址,但不能用在常量上。

#include "stdafx.h"

void Test()
{
	char a = 10;
	short b = 20;
	int c = 30;

	//& 可以取任何一个变量的地址
	//&a 的类型 就是a的类型  +   *

	//&b == b的类型  + *   short*
	//&c == int *
	char* pa;
	short* pb;
	int* pc;
	pa = &a;
	pb = &b;
	pc = &c;

}

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

反汇编代码如下:

0040B4E0 55                   push        ebp
0040B4E1 8B EC                mov         ebp,esp
0040B4E3 83 EC 58             sub         esp,58h
0040B4E6 53                   push        ebx
0040B4E7 56                   push        esi
0040B4E8 57                   push        edi
0040B4E9 8D 7D A8             lea         edi,[ebp-58h]
0040B4EC B9 16 00 00 00       mov         ecx,16h
0040B4F1 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
0040B4F6 F3 AB                rep stos    dword ptr [edi]
0040B4F8 C6 45 FC 0A          mov         byte ptr [ebp-4],0Ah
0040B4FC 66 C7 45 F8 14 00    mov         word ptr [ebp-8],offset Test+20h (0040b500)
0040B502 C7 45 F4 1E 00 00 00 mov         dword ptr [ebp-0Ch],1Eh
0040B509 8D 45 FC             lea         eax,[ebp-4]
0040B50C 89 45 F0             mov         dword ptr [ebp-10h],eax
0040B50F 8D 4D F8             lea         ecx,[ebp-8]
0040B512 89 4D EC             mov         dword ptr [ebp-14h],ecx
0040B515 8D 55 F4             lea         edx,[ebp-0Ch]
0040B518 89 55 E8             mov         dword ptr [ebp-18h],edx
0040B51B 5F                   pop         edi
0040B51C 5E                   pop         esi
0040B51D 5B                   pop         ebx
0040B51E 8B E5                mov         esp,ebp
0040B520 5D                   pop         ebp
0040B521 C3                   ret

  • 针对于&符号,汇编语言中通过lea指令来赋予地址。lea指令在汇编语言中的意思是取地址,即 将地址序号赋给寄存器或者别的内存。而这里是将ebp-4也就是我们函数中的第一个局部变量的地址先赋值给寄存器,然后再通过寄存器给数据宽度为dword类型的ebp-10h,也就是我们的char* pa;其他同理
#include "stdafx.h"

void Test()
{
	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;				


}

int main(int argc,char* argv[])
{
	Test();
	return 0;
}
  • 刚刚讲过&符号,指的是将原先的x变量类型 + 符号*

    1. 这里的&a,是char类型加上一个*符号给带 * 类型的pa变量。
    2. 后面的&pa,指的是char*类型加上一个 * 符号,也就是char** pa赋给char** ppa;
  • 所以最终可以简写为

    • char ** ppa = &pa;
    • char** ppb = &pb;
    • char** ppc = &pc;

案例二

#include "stdafx.h"

int x = 10;

void Test()
{
	int* y = (int*)&x;
}

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

我们之前讲过给带 * 类型的变量赋值时,必须指明赋值的类型。但是这里有一个关键点,&x表示的是x的变量类型加上一个*.这里已经是int *类型了。所以这里不需要重复指明赋值的类型了。直接简写成int * y = &x即可;

汇编代码如下:

0040B4E0 55                   push        ebp
0040B4E1 8B EC                mov         ebp,esp
0040B4E3 83 EC 44             sub         esp,44h
0040B4E6 53                   push        ebx
0040B4E7 56                   push        esi
0040B4E8 57                   push        edi
0040B4E9 8D 7D BC             lea         edi,[ebp-44h]
0040B4EC B9 11 00 00 00       mov         ecx,11h
0040B4F1 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
0040B4F6 F3 AB                rep stos    dword ptr [edi]
0040B4F8 C7 45 FC 04 1B 42 00 mov         dword ptr [ebp-4],offset x (00421b04)
0040B4FF 5F                   pop         edi
0040B500 5E                   pop         esi
0040B501 5B                   pop         ebx
0040B502 8B E5                mov         esp,ebp
0040B504 5D                   pop         ebp
0040B505 C3                   ret

这里的x是全局变量,我们在内存窗口中查看一下这个0x00421b04地址的值试一试

image-20230628095026153

可以看到此时全局变量0x00421b04地址所存储的值为0A,也就是十进制的10.

image-20230628095127035

而此时ebp-4后的地址的值为0xCCCCCCCC.当我们执行了这一条mov指令后,我们发现ebp-4地址存储的是全局变量的地址。

而在C语言对应的代码为int* y = &x;

image-20230628095216432

所以这里实际上的意思是,int* 类型的y变量存储的x全局变量的地址,即0x421B04.

十一、带*类型的求值

代码如下:

#include "stdafx.h"



void Test()
{
	int* px = (int*)10;

	int** px2 = (int**)10;

	int*** px3 = (int***)10;

	int**** px4 = (int****)10;

	*(px); //是什么类型?

	*(px2);	//*(px2)是什么类型?

	*(px3); //*(px3)是什么类型?

	*(px4); //*(px4)是什么类型?

	int x = 10;
	int y = *x;

}


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

编译器报错如下

  • error C2100: illegal indirection

对于int y = x;代码而言是无效的。对于变量前加 * 而言,其原来的变量必须至少有一个及以上。如果原来变量没有 * 类型,我们就不能在变量前加 *.

案例二:

代码如下

#include "stdafx.h"

void Test()
{
	int x = 10;
	int* y = &x;
	char x1 = *y;

}


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

汇编代码如下:

0040B4E0   push        ebp
0040B4E1   mov         ebp,esp
0040B4E3   sub         esp,4Ch
0040B4E6   push        ebx
0040B4E7   push        esi
0040B4E8   push        edi
0040B4E9   lea         edi,[ebp-4Ch]
0040B4EC   mov         ecx,13h
0040B4F1   mov         eax,0CCCCCCCCh
0040B4F6   rep stos    dword ptr [edi]
5:        int x = 10;
0040B4F8   mov         dword ptr [ebp-4],0Ah
6:        int* y = &x;
0040B4FF   lea         eax,[ebp-4]
0040B502   mov         dword ptr [ebp-8],eax
7:        char x1 = *y;
0040B505   mov         ecx,dword ptr [ebp-8]
0040B508   mov         dl,byte ptr [ecx]
0040B50A   mov         byte ptr [ebp-0Ch],dl
8:
9:    }
0040B50D   pop         edi
0040B50E   pop         esi
0040B50F   pop         ebx
0040B510   mov         esp,ebp
0040B512   pop         ebp
0040B513   ret

  • int x = 10

    1. 首先的话通过mov dword ptr[ebp-4],0Ah,给int类型的x变量赋值为10

    2. 赋值前

    1. 赋值后

  • int* y = &x

    1. 首先将ebp-4的地址通过lea指令赋值给eax

    1. 随后将eax所存储的地址赋给ebp-8,ebp-8是我们的int* y变量

  • char x1 = *y

    1. 首先将ebp-8所存储的地址赋给ecx

  1. 然后将ecx为内存编号的内存所存储的一个byte的值赋值给dl寄存器

  1. 最后将dl寄存器赋值给ebp-0Ch.

实际上在汇编中,对于*符号以及&符号,编译器一般会用lea指令,以及寄存器间接寻址来实现该符号的功能

总结:

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

十二、用带*类型操作数组

int arr[5] = {1,2,3,4,5};
我们知道arr[0] 指的是数组名为arr,下标为0,arr数组的第一个元素
那么之前我们学过了&符号

&arr[0]代表什么呢?
&arr[0]代表数组第一个元素的首地址,也可以称之为数组的首地址
那么既然&arr[0]代表的是一个数组的地址。
我们可以用int * x来存储

int* x = &arr[0];
还有一种简便的写法,就是编译器为我们所提供的一种,数组名也可以为数组的首地址

int* x = &arr[0];等价于int* x = arr;

以下是带*类型操作数组的三种案例:

第一种代码如下:

#include "stdafx.h"

void Test()
{	
	char arr[5] = {1,2,3,4,5};
	char*pa = arr;
	for(char i=0;i<5;i++)
	{
		printf("%d\n",*(pa+i));//输出结果1,2,3,4,5
	}
}


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

这里我们来详细分析一下代码。

  • char* pa = arr;
    1. pa是char* 类型的变量,而arr指的是char类型arr数组的首地址。
    2. 也就是这句代码其实等价于char* pa =&arr === char* pa = &arr[0]
  • *(pa+i)
    1. pa存储的是arr数组的地址,而i是作为索引的下标。
    2. 之前我们讲过带*类型的变量加上或者减去一个整数
      1. 类型变量 + N = 带 * 类型变量 + N(去掉一个 * 后类型的宽度)
      2. 所以这里应该是 = arr首地址 + i * char类型的数据宽度
    3. *代表去pa变量,去掉一个 * 后的值。
#include "stdafx.h"

void Test()
{	
	short arr[5] = {1,2,3,4,5};
	short*pa = arr;
	for(char i=0;i<5;i++)
	{
		printf("%d\n",*(pa+i));//输出结果为1,2,3,4,5
	}

}


int main(int argc,char* argv[])
{
	Test();
	return 0;
}
#include "stdafx.h"

void Test()
{
	int a[5] = {1,2,3,4,5};
	int* pa = a;
	for(int i=0;i<5;i++)
	{
		printf("%d\n",*(pa+i));
	}

}


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

上面二个跟第一个同理,至少操作数组的*类型变为了short类型,和int类型

总结:

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

数组作为参数

代码如下

#include "stdafx.h"

void PrintArray(int arr[],int nLength)				
{				
	for(int i=0;i<nLength;i++)			
	{			
		printf("%d\n",arr[i]);		
	}			
}				

int main(int argc,char* argv[])
{
	int arr[5] = {1,2,3,4,5};
	int nLength = sizeof(arr) / 4;
	printf("%d\n",nLength);
	PrintArray(arr,nLength);
	return 0;
}

汇编代码如下:

main:
0040B520   push        ebp
0040B521   mov         ebp,esp
0040B523   sub         esp,58h
0040B526   push        ebx
0040B527   push        esi
0040B528   push        edi
0040B529   lea         edi,[ebp-58h]
0040B52C   mov         ecx,16h
0040B531   mov         eax,0CCCCCCCCh
0040B536   rep stos    dword ptr [edi]
0040B538   mov         dword ptr [ebp-14h],1
0040B53F   mov         dword ptr [ebp-10h],2
0040B546   mov         dword ptr [ebp-0Ch],3
0040B54D   mov         dword ptr [ebp-8],4
0040B554   mov         dword ptr [ebp-4],5
0040B55B   mov         dword ptr [ebp-18h],5
0040B562   mov         eax,dword ptr [ebp-18h]
0040B565   push        eax
0040B566   push        offset string "%x\n" (0041f10c)
0040B56B   call        printf (0040b7e0)
0040B570   add         esp,8
0040B573   mov         ecx,dword ptr [ebp-18h]
0040B576   push        ecx
0040B577   lea         edx,[ebp-14h]
0040B57A   push        edx
0040B57B   call        @ILT+15(FindBloodAddr) (00401014)
0040B580   add         esp,8
0040B583   xor         eax,eax
0040B585   pop         edi
0040B586   pop         esi
0040B587   pop         ebx
0040B588   add         esp,58h
0040B58B   cmp         ebp,esp
0040B58D   call        __chkesp (004010d0)
0040B592   mov         esp,ebp
0040B594   pop         ebp
0040B595   ret

PrintArray:
0040B860   push        ebp
0040B861   mov         ebp,esp
0040B863   sub         esp,44h
0040B866   push        ebx
0040B867   push        esi
0040B868   push        edi
0040B869   lea         edi,[ebp-44h]
0040B86C   mov         ecx,11h
0040B871   mov         eax,0CCCCCCCCh
0040B876   rep stos    dword ptr [edi]
0040B878   mov         dword ptr [ebp-4],0
0040B87F   jmp         PrintArray+2Ah (0040b88a)
0040B881   mov         eax,dword ptr [ebp-4]
0040B884   add         eax,1
0040B887   mov         dword ptr [ebp-4],eax
0040B88A   mov         ecx,dword ptr [ebp-4]
0040B88D   cmp         ecx,dword ptr [ebp+0Ch]
0040B890   jge         PrintArray+4Bh (0040b8ab)
0040B892   mov         edx,dword ptr [ebp-4]
0040B895   mov         eax,dword ptr [ebp+8]
0040B898   mov         ecx,dword ptr [eax+edx*4]
0040B89B   push        ecx
0040B89C   push        offset string "%x\n" (0041f10c)
0040B8A1   call        printf (0040b7e0)
0040B8A6   add         esp,8
0040B8A9   jmp         PrintArray+21h (0040b881)
0040B8AB   pop         edi
0040B8AC   pop         esi
0040B8AD   pop         ebx
0040B8AE   add         esp,44h
0040B8B1   cmp         ebp,esp
0040B8B3   call        __chkesp (004010d0)
0040B8B8   mov         esp,ebp
0040B8BA   pop         ebp
0040B8BB   ret

  • 0040B538 mov dword ptr [ebp-14h],1
    0040B53F mov dword ptr [ebp-10h],2
    0040B546 mov dword ptr [ebp-0Ch],3
    0040B54D mov dword ptr [ebp-8],4
    0040B554 mov dword ptr [ebp-4],5
    1. 给数组赋初值1,2,3,4,5
  • 0040B55B mov dword ptr [ebp-18h],5
    1. sizeof(arr)是计算整个arr所有元素的长度,4是每个元素所占长度。sizeof(arr) / 4就是arr数组的长度
    2. 将sizeof(arr) / 4后的结果赋值给nLength
  • 0040B573 mov ecx,dword ptr [ebp-18h]
    0040B576 push ecx
    1. 将nLength长度赋值给ecx寄存器,然后通过push指令压栈
  • 0040B577 lea edx,[ebp-14h]
    0040B57A push edx
    1. ebp-14h是我们arr数组的第一个元素。通过lea指令将第一个元素的地址赋值给edx寄存器,然后压栈
    2. 也就是说我们在函数中传入数组的时候,传递的是数组元素的首地址
  • Printarray函数里面就是常见的for循环汇编结构了。

总结:

  1. 数组作为参数时,一定要传递数组首元素的地址
  2. 传递数组首元素的地址同时,也要把数组长度传进去。

十三、指针遍历例子

模拟实现CE的数据搜索功能:

这一堆数据中存储了角色的血值信息,假设血值的类型为int类型,值为100(10进制)
请列出所有可能的值以及该值对应的地址.

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	};				

按照我们常规的思路来解的话,代码如下

#include <stdio.h>
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	};		

void FloodFindAddr()
{
    for(int i = 0; i<100;i++)
    {
        if(data[i] == 0x64)
        {
            printf("%x\n",&data[i]);
        }
    }
    
}

int main()
{
    FloodFindAddr();
    return 0;
}
/*char类型输出结果
422322
42232a
422353
42235f
422362
*/

​ short类型的0x100

#include "stdafx.h"


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 FindBloodAddr()
{
	short* p = (short*)data;
	for(int i =0;i<50;i++)
	{
	
		if(*(p + i) == 0x64)
		{
			printf("%x\n",p+i);
		}
	}

}


int main(int argc,char* argv[])
{
	FindBloodAddr();
	return 0;
}
//输出结果
/*
422322
422362
*/

int类型的0x100

#include "stdafx.h"


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 FindBloodAddr()
{
	int* p = (int*)data;
	for(int i =0;i<25;i++)
	{
	
		if(*(p + i) == 0x64)
		{
			printf("%x\n",p+i);
		}
	}

}


int main(int argc,char* argv[])
{
	FindBloodAddr();
	return 0;
}
//输出答案为空
  • 可以看到当我们以char类型遍历时,我们可以发现5个0x64值
  • 当我们以short类型进行遍历时,我们可以发现2个0x64值
  • 当我们以int类型进行遍历时,没有

难道是真的没有int类型的0x64值吗?并不是,上面给出的所有数据中,在实际应用中,int类型的数据不一定就像上面一样,每4个算一个Int,内存的分配每个程序并不是固定的。很有可能它从第二个第三个第四个第五个才构成int.

十四、字符串及字符数组空字符

C语言字符串写法如下:

char names[] = "ABCDE";

C语言字符数组写法如下:

char names[6] = {'A','B','C','D','E','0'};//0可以省略,也可以写成\0

代码如下:

#include "stdafx.h"

void Test()
{
	char data[] = "ABCDE";
}

int main(int argc,char* argv[])
{
	Test();
	return 0;
}
  • 可以观察到在data数组中,我定义了一个字符串ABCDE,我们去调试一下,看data数组中是否存储着这几个字符

这里可得知data数组的首地址为0x0041ff6c,我们在内存窗口中,可以发现ABCDE字符串所对应的ASCII码的十六进制。但编译器是如何读取的呢?

在变量窗口中,我们可以发现一共有6个字符,最后一个是0,即空字符。这个空字符是编译器默认为我们填上去的。

所以当我们用printf函数%s格式化打印字符串的时候,编译器会帮我们一直读到空字符0,这里会停下来。

实际上我们一共打印了6个字符,最后一个字符是空字符,是编译器为我们添加的。

我们再来进行进一步的论证:

#include "stdafx.h"

void Test()
{
	char arr[6] = {'A','B','C','D','E','\0'}; //也可以直接写 0 					
					
	char names[] = "ABCDE";										
	printf("%s\n",arr);										
	printf("%s\n",names);					
}

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

按照我们常规的思路,names数组打印的字符串为ABCDE,而arr数组我们添加了一个\0空字符,那么是否我两个数组打印的字符串不一样呢?

输出的结果一样,我们继续在内存窗口和变量窗口进行进一步的分析

  • names数组的地址为0x0041ff70,在内存窗口中为ABCDE字符所对应的十六进制ASCII码。
  • 变量窗口中,一共有6个字符,最后一个是空字符。

  • arr数组首地址为0x41ff6c,内存窗口中为ABCDE字符所对应的HexASCII码
  • 变量窗口同上。

总结:

  1. 编译器后在后面添一个 00 做为字符串的结束标记。
  2. %s:打印一个字符串,直到内存为 0 为止
  3. 字符数组如果打印字符串的话,需要手动加上\0。字符串不需要自己加0,编译器会为我们添加空字符

十五、常量区

内存分布图如下所示:

案例一:

char* x = "china";				
char y[] = "china";				
void Function()				
{				
				
	*(x+1) = 'A';			
				
	y[1] = 'A';			
}				
//哪个可以修改?哪个不可以修改呢?

执行程序后,发现报错0xC0000005.网上查了之后,发现是内存读写错误,访问非法空间。

我们把*(x+1) = 'A'注释掉,发现可以正常编译运行了。

  • x是char* 类型的指针,其执行的是"china"字符串的首地址
#include "stdafx.h"

char* x = "china";	//存储的是china字符串所在的地址			
char y[] = "china";		//y是存储的china常量字符串		
void Function()				
{				
	x = "abc";			
	//*(x+1) = 'A';

	y[1] = 'A';
}				
				


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

我们换一种思路,让x指向另外字符串的地址,看看能不能进行编译运行。发现程序可以正常编译的。

  • x是char*类型的变量,存储的是别人的地址,可以随意更改
  • 字符串"china"是常量,是不能更改的
  • 也就是说x我是可以随意更改它指向的地址的,但是它指向的字符串我是不能随意更改的。

x是指向china常量的一个指针,x可以指向别的变量的地址,china为常量字符串。我们不能通过x来改变常量字符串。因为常量区是可读,不可写的.

那为什么y字符串数组可以更改呢?

  • y字符串数组位于全局区
  • 全局区可读,可写
  • 所以我可以随意修改y数组元素的值

案例二

#include "stdafx.h"

void Function()
{
	char* a = "china";

}


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

Function汇编代码如下:

00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,44h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-44h]
0040102C   mov         ecx,11h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
00401038   mov         dword ptr [ebp-4],offset string "china" (0041f01c)
0040103F   pop         edi
00401040   pop         esi
00401041   pop         ebx
00401042   mov         esp,ebp
00401044   pop         ebp
00401045   ret

可以看到Function函数的堆栈一共分配了0x44h个字节.

mov         dword ptr [ebp-4],offset string "china" (0041f01c)
  • 将常量字符串的首地址给char * 变量
  • 带*类型的变量都为4字节

案例三

#include "stdafx.h"

void Function()
{
	char y[] = "ABCDE";

}


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

汇编代码如下:

Function:
0040B4E0   push        ebp
0040B4E1   mov         ebp,esp
0040B4E3   sub         esp,48h
0040B4E6   push        ebx
0040B4E7   push        esi
0040B4E8   push        edi
0040B4E9   lea         edi,[ebp-48h]
0040B4EC   mov         ecx,12h
0040B4F1   mov         eax,0CCCCCCCCh
0040B4F6   rep stos    dword ptr [edi]
0040B4F8   mov         eax,[string "china" (0041f01c)]
0040B4FD   mov         dword ptr [ebp-8],eax
0040B500   mov         cx,word ptr [string "china"+4 (0041f020)]
0040B507   mov         word ptr [ebp-4],cx
0040B50B   pop         edi
0040B50C   pop         esi
0040B50D   pop         ebx
0040B50E   mov         esp,ebp
0040B510   pop         ebp
0040B511   ret
  • Function函数堆栈分配了48h字节

同时我们知道china是一个字符串,一共有5个字节。编译器是如何分配的呢?

0040B4F8   mov         eax,[string "china" (0041f01c)]

可以发现在china字符串首地址为0x41F01C.一开始从0x0041f01c地址处的内存的4字节先赋值给eax。

  • eax是32位寄存器,最多存储FFFFFFFF,4字节
  • 这里是将字符ABCD先存入EAX中

那最后一个字节E怎么处理的呢?这里是通过首地址+4来寻址最后一个字节。

然后存入到cx里面

总结:

  • ​ 常量字符串的拷贝
  • char* x = "china";
    1. x是指向字符串的地址,可写
    2. china是常量区的字符串,不可写入
  • char y[]
    1. y数组是从常量区把字符串拷贝到堆栈里面来
    2. 可写

十六、字符串常用操作


1、int strlen (char* s)			
返回值是字符串s的长度。不包括结束符'/0'。			
			
2、char* strcpy (char* dest, char* src);			
复制字符串src到dest中。返回指针为dest的值。			
			
3、char* strcat (char* dest, char* src);			
将字符串src添加到dest尾部。返回指针为dest的值。			
			
4、int strcmp ( char* s1, char* s2);			
/*
一样返回0  不一样返回1

自己写代码,完成上述函数的实现
*/

Strlen函数

作用:计算长度

自己用代码实现:

int strlen(char* s)
{
	int count = 0;
	while(1)
	{
		if(*(s++) != '\0')
		{
			count++;
		}
		else
		{
			break;
		}

	}
	return count;
}

超简便写法:

int strlen (char* s)		
{		
	int len = 0;	
	while(*(s) != 0)	
	{	
		len++;
		s++;
	}	
	return len;	
}		

Strcpy函数

作用:拷贝一块内存的所有值到另一块内存去

自己代码实现如下:

char* strcpy (char* dest, char* src)
{
	while(1)
	{
		if(*src != '\0')
		{
			*dest++ = *src++;
		}
		else
		{
			break;
		}
	}
	return dest;
}

超简便写法:

char* strcpy (char* dest,char* src)			
{			
	while((*(dest++)=*(src++))!=0);		
			
	return dest;		
}			

Strcat函数

作用:拼接字符串

自己代码实现:

char* strcat (char* dest, char* src)
{
	char* ret = dest;
	int i = 0;
	while(1)
	{
		if(*(dest+i) == '\0')
		{
			break;
		}
		i++;
		
	}
	ret = dest + i;
	while(1)
	{
		if(*src != '\0')
		{
			*ret++ = *src++;
		}
		else
		{
			break;
		}
	}
	return ret;
}

超简便实现

char* strcat (char* dest, char* src)			
{			
	while(*dest != '\0')		
		dest++;	
	while((*dest++ = *src++)!='\0');		
			
	return dest;		
}			

Strcmp函数

作用:作比较,一样返回0,不一样返回1

自己代码实现:

int strcmp ( char* s1, char* s2)
{
	int i = 0;
	while(1)
	{
		if(*(s1 + i) != *(s2 + i))
		{
			return 1;
		}
		else if(*(s1 + i) == *(s2 + i) == '\0')
		{
			return 0;
		}
		i++;
	}
}

超简便写法:

int strcmp(char* s1, char* s2)			
{			
	while(*s1 != '\0' && *s2 != '\0')		
	{		
		if(*s1 != *s2)	
		{	
			return 1;
		}	
		s1++;	
		s2++;	
	}		
	if(*s1 == '\0' && *s2 == '\0')		
	{		
		return 0;	
	}		
	else		
	{		
		return 1;	
	}		
}			

十七、指针函数

指针函数只是一个函数的前面加了一个*类型。返回值必须是指针而已。其原理没很大差别

十八、指针遍历例子二

模拟实现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						

1、编写函数,返回角色名字信息的地址,如果没有返回0					
					
char* FindRoleNameAddr(char* pData,char* pRoleName)					

2、编写函数,遍历上面数据中所有角色名字.				
				
char find(char* p, char* str)				

1、实现思路:

既然要从一堆数据里面去找到字符串WOW,我们不能用以前老方法,从头到尾用int去寻找。

我们可以先每次选取四个字节,四个字节里面依次与WOW进行匹配,如果匹配失败,则选取下一组4字节。

代码实现:

#include <stdio.h>
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) {
    int roleNameLength = 0;//长度
    char* pRoleNameIter = pRoleName;//将要搜索的角色名的地址赋给pRoleNameIter指针
    while (*pRoleNameIter != '\0') {//做长度的累加
        roleNameLength++;
        pRoleNameIter++;
    }

    char* pDataIter = pData;//将数据首地址给pDataIter
    while (*pDataIter != '\0') {
        int match = 1;//用来做是否有角色地址判断的
        char* pDataTemp = pDataIter;
        char* pRoleNameTemp = pRoleName;
        for (int i = 0; i < roleNameLength; i++) {//相当于每四个字节中,做一次判断,是否与
            if (*pDataTemp != *pRoleNameTemp) {
                match = 0;
                break;
            }
            pDataTemp++;
            pRoleNameTemp++;
        }
        if (match == 1) {
            return pDataIter;
        }
        pDataIter += 4;  // 每次移动4字节
    }

    return NULL;
}


int main() {

    
    char roleName[] = "WOW";

    char* pData = data;
    char* pEnd = data + sizeof(data) - 1;  // 减1是为了跳过末尾的'\0'

    while (pData + 3 < pEnd) {  // 保证至少有4个字节的空间
        char* result = FindRoleNameAddr(pData, roleName);
        if (result != NULL) {
            printf("Role name address: %p , the value is %#X%#X%#X\n", (void*)result,*(result),*(result+1),*(result+2),*(result+3));
            break;
        }
        pData++;
    }

    if (pData + 3 >= pEnd) {
        printf("Role name not found.\n");
    }


    getchar();
    return 0;
}
//输出结果Role name address: 0008A048 , the value is 0X570X4F0X57

2、遍历所有角色名称

  • 游戏角色名称肯定是可见字符
  • 数据里面存在大量数字0

针对上面两个点,我们可以通过限制字符在32到128之间的可见字符。可能有些可打印字符是连一起的,有些不是,我们可以用二个嵌套循环来解决这个问题

代码如下:

#include <stdio.h>
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
};



void find(char* p1, char* str)
{
    char* p = p1;
    int i = 0;
    while(i < sizeof(data))
    {
        if (*p >= 0x20 && *p <= 0x7E) 
        {
            printf("Character name: ");
            while (*p >= 0x20 && *p <= 0x7E) 
            {
                printf("%c", *p);
                p++;
                i++;
            }
            printf("\n");
        }
        else 
        {
            p++;
            i++;
        }
    }
}

 


int main() {

   
    char* pData = data;
    char name[] = "0000";
    find(pData, name);


    getchar();
    return 0;
}
/*输出结果如下:
Character name:
Character name: D
Character name: 3
Character name: G
Character name: d
Character name: d
Character name: t
Character name: A
Character name: WOW
Character name: d
Character name: #
Character name: d
Character name: d

*/

十九、指针数组

在学习指针数组之前,我们先来回忆一下数组的知识点

#include "stdafx.h"

int main(int argc,char* argv[])
{
	int arr[5] = {0};
	char arr1[5] = {0};
	short arr2[5] = {0};

	return 0;
}
  1. 程序为堆栈分配一共0x68H,去除初始的40H,一共有0x28H(40)是数组分配的

    • int arr[5],int类型数组,一共有五个元素,5 * 4 = 20Bit
    • char arr1[5],char类型数组,虽然一共有五个元素,前四个元素填满一个int内存后,最后一个填第二个Int类型内存,但是要补齐,所以最后一共是5 * 1 + 3(需要补齐int类型的3个字节) = 8Bit
    • short arr2[5],同上,但是要补齐int宽度的内存,5 * 2 + 2(需要补齐的2个字节) = 12Bit

    一共分配40个字节。

    • ​ arr等价于 &arr[0].

那么指针数组是什么呢?

之前我们讲过指针就是变量类型前面加上一个或多个*的变量而已,我们来试试.

代码如下:

#include "stdafx.h"

int main(int argc,char* argv[])
{
	int* arr[5] = {0};

	printf("%d\n",arr);

	return 0;
}




汇编代码如下:

0040BA5D   lea         ecx,[ebp-14h]
0040BA60   push        ecx
0040BA61   push        offset string "pData+i = %p,  *(pData+i) = %d\n" (0042001c)
0040BA66   call        printf (00401250)
0040BA6B   add         esp,8

这里会通过lea指令,将数组指针指向的首地址,给打印出来,如果打印的值的话,我们用*arr++符号即可。

案例二:

#include "stdafx.h"

int main(int argc,char* argv[])
{
	int a = 10;
	int b = 10;
	int c = 10;
	int d = 10;
	int e = 10;

	int* p1 = &a;
	int* p2 = &b;
	int* p3 = &c;
	int* p4 = &d;
	int* p5 = &e;
	int* arr[5] = {p1,p2,p3,p4,p5};
	for(int i = 0;i<5;i++)
	{
		printf("%x\n",*(arr+i));
	}


	getchar();
	return 0;
}
/*打印结果如下
18ff44
18ff40
18ff3c
18ff38
18ff34
*/

上面也是一种常见的数组指针,我们先通过定义5个int类型变量,然后设置5个int*类型变量,指向他们。

所以arr数组存储的是指向这5个int类型变量的各个地址。

下面是一个常见的指针数组案例:

#include "stdafx.h"

int main(int argc,char* argv[])
{
	char* arr[5]={
		"if",
		"while",
		"for",
		"do..while"
	};
	for(int i=0;i<5;i++)
	{
		printf("%x\n",arr+i);
	}
	getchar();
	return 0;
}
/*
输出结果如下
18ff34
18ff38
18ff3c
18ff40
18ff44
*/

我们来想一下,这里的arr指针数组,编译器会分配多少字节呢?

这里和字符数组不一样的是,数组里面存储的都是int类型,即存储的都是常量字符串的地址,所以这里所占字节为20+4,这里还有一个空间并没有赋值。

字符串指针数组案例:

#include "stdafx.h"

int main(int argc,char* argv[])
{
	char str = 'A';
	char* p = &str;
	char arr[5] = {'c','h','i','n','a'};
	printf("%s\n",arr);

	getchar();
	return 0;
}

打印结果:

为什么会这样呢?

  • arr字符串数组里面没有空字符
  • %s是打印到空字符才停止,所以这里会打印出数组越界后的其他垃圾数据

二十、结构体指针

先来回顾一遍结构体和指针的相关知识.

代码如下:

#include "stdafx.h"


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

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

	AAA* Args = (AAA*)100;
	Args++;
	printf("%d\n",Args);
	Args--;
	printf("%d\n",Args);

	getchar();
	return 0;
}
/*
输出结果分别为112,100为什么呢?
*/
  • 对带*类型进行赋值的时候,前面的类型必须是一样的,这里的100必须是(AAA *)
  • 带*类型进行++或--,是需要去掉一个 * 后进行运算的
    • AAA*去掉一个 * 后为结构体,所对应的字节数为12个,所以这里应该为100 + 12
    • --同理,112-12即可。

那么我们是如何通过结构体指针来访问结构体内对象的值呢?

我们先来定义一个结构体,然后让结构体的地址赋给一个结构体指针,代码如下:

#include "stdafx.h"

struct AAA
{
	int a;
	short b;
	char c;
};

int main(int argc,char* argv[])
{
	AAA s;
	s.a = 10;
	s.b = 20;
	s.c = 30;

	AAA* sss = &s;
	printf("%d %d %d\n",sss->a,sss->b,sss->c);


	getchar();
	return 0;
}
/*
输出结果为10 20 30
*/

这里我把s结构体的地址赋给了AAA*类型变量。s原先的数据类型是AAA,即结构体,加了&地址后,变为了AAA *(前面讲过&符号).

这里我们如果通过结构体指针来访问对象里面的值的话,需要用符号->.

如果我们要访问上面代码中结构体中a的值的话,运用结构体指针如下所示:

  • sss->a,其他同理。

还有一个问题,难度我结构体指针一定要指向结构体吗?难道不可以其他类型的变量么?我们来试一试

#include "stdafx.h"

struct AAA
{
	int a;
	short b;
	char c;
};

int main(int argc,char* argv[])
{
	int a = 10;

	AAA* sss = (AAA*)&a;
	printf("%d %d %d\n",sss->a,sss->b,sss->c);
	getchar();
	return 0;
}
/*
10 -120 24

*/

可以发现,我们结构体的指针其实是可以指向其他类型的变量,只是指向的可能是其他垃圾数据而已。

二十一、结构体指针和指针数组练习

1、创建一个int* arr[5] 数组,并为数组赋值(使用&).					


2、创建一个字符指针数组,存储所有的C的关键词(查资料找),并全部打印出来.							
							
3、查找这些数据中,有几个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;					

第一题代码实现:

#include "stdafx.h"

//创建一个int* arr[5] 数组.,并为数组赋值(使用&)		
void Test()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	int e = 50;
	int* arr[5] = {&a,&b,&c,&d,&e};
	for(int i = 0;i < 5;i++)
	{
		printf("%x\n",arr+i);
	}
}

int main(int argc,char* argv[])
{
	Test();
	
	getchar();
	return 0;
}
/*
输出结果如下
18fec8
18fecc
18fed0
18fed4
18fed8
*/

第二题代码实现:

注意:在VC6中,指针数组不会出现const char * 到char * 类型的错误。但是Vs如2022是会出现报错的,修改char* 变为const char*即可。

VC6代码:

#include "stdafx.h"

int main(int argc,char* argv[])
{
	char* arr[]={
		"auto", "double", "int", "struct", "break", "else", "long", "switch", "case", "enum", "register", 
	"typedef", "char", "extern", "return", "union", "const", "float", "short", "unsigned", "continue", 
	"for", "signed", "void", "default", "goto", "sizeof", "volatile", "do", "if", "static", "while",'\0'
	};
	int i = 0;
	while(*(arr + i) != '\0')
	{
		printf("%s\n",*(arr + i));
		i++;
	}
	getchar();
	return 0;
}

VS2022代码:

#include <stdio.h>


int main(int argc, char* argv[])
{
	const char* arr[] = {
		"auto", "double", "int", "struct", "break", "else", "long", "switch", "case", "enum", "register",
	"typedef", "char", "extern", "return", "union", "const", "float", "short", "unsigned", "continue",
	"for", "signed", "void", "default", "goto", "sizeof", "volatile", "do", "if", "static", "while","\0"
	};
	int i = 0;
	while (*(arr + i) != (const char*)'\0')
	{
		printf("%s\n", *(arr + i));
		i++;
	}
	getchar();
	return 0;
}

第三题代码实现:

#include "stdafx.h"

//查找这些数据中,有几个id=1 level=8的结构体信息。					
					
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	
};

typedef struct TagPlayer					
{					
	int id;				
	int level;				
}Player;					

Player s;



int FindData(char* pData,Player* a)
{
	char* Data = pData;
	Player* key = a;
	int keyLength = 4;

	char* DataTemp = Data;
	Player* keyTemp = key;
	
	while(*DataTemp != '\0')
	{
		char* DataTempIter = DataTemp;
		int match = 1;
		for(int i = 0;i < keyLength;i++)
		{
			if((*DataTempIter == keyTemp->id) || (*DataTempIter ==  keyTemp->level) )
			{
				if((*Data == keyTemp->id) || (*Data ==  keyTemp->level) )
				{
					break;
				}
				else
				{
					match = 0;
					break;
				}
				DataTemp += 4;
			}
			else
			{
				match = 0;
				break;
			}
			DataTempIter++;
		}
		if(match == 1)
		{
			return match;
		}
		DataTemp += 4;
	}


	
}

int main(int argc,char* argv[])
{
	s.id = 1;
	s.level = 8;
	int count = 0;
	Player* keyword = &s;
	char* pData = Data;
	char* pEnd = Data + sizeof(Data) - 1;//去掉空字符
	while(pData + 3 < pEnd)
	{
		int counthave = FindData(pData,keyword);
		if(counthave != 0)
		{
			count++;
		}
		pData++;
	}

	if(pData + 3 >= pEnd)
	{
		printf("Error");
	}
	printf("The match value == %d\n",count);


	getchar();
	return 0;
}

二十二、多级指针

案例一:

#include "stdafx.h"

void Test()
{
	char* p1;		
	char** p2;		
	char*** p3;		
	char**** p4;		
	char***** p5;		
	char****** p6;		
	char******* p7;	
	
	printf("%p\n",p1);


}


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

	getchar();
	
	return 0;
}
/*
打印的结果为CCCCCCCC
*/

打印结果为CCCCCCCC的原因是,char * p1并没有指向任何类型,被称之为野指针.即指向非法变量空间的指针

案例二:

#include "stdafx.h"

void Test()
{
	char* p1;		
	char** p2;		
	char*** p3;		
	char**** p4;		
	char***** p5;		
	char****** p6;		
	char******* p7;	
	
	printf("%d\n",*p1);

	printf("%d\n",*(p1+0));


}


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

	getchar();
	
	return 0;
}
/*
首先是地址报错。

这里我们虽然定义了多个带*类型的变量,但是我们既没有给变量其指向的变量,也没有赋值。
所以不管是我打印带*变量的地址,还是带*变量所指向的值,都可能是内存中的非法数据
*/

反汇编如下:

13:       printf("%d\n",*p1);
0040BE68   mov         eax,dword ptr [ebp-4]
0040BE6B   movsx       ecx,byte ptr [eax]
0040BE6E   push        ecx
0040BE6F   push        offset string "%p\n" (00420f80)
0040BE74   call        printf (00401110)
0040BE79   add         esp,8
14:
15:       printf("%d\n",*(p1+0));
0040BE7C   mov         edx,dword ptr [ebp-4]
0040BE7F   movsx       eax,byte ptr [edx]
0040BE82   push        eax
0040BE83   push        offset string "%p\n" (00420f80)
0040BE88   call        printf (00401110)
0040BE8D   add         esp,8

  • printf("%d\n",*p1);
    • 这里是首先将地址赋值到eax寄存器里面来
    • 将eax寄存器里面所存储的地址的所指向的值赋值给ecx
  • printf("%d\n",*(p1+0));
    • 与上面同理。

所以我们可以发现*p1和 * (p1+0).所生成的反汇编代码一样。我们可以得出第一个结论

  • *p1 = *(p1+0)

案例三:

#include "stdafx.h"

void Test()
{
	char* p1;		
	char** p2;		
	char*** p3;		
	char**** p4;		
	char***** p5;		
	char****** p6;		
	char******* p7;	
	
	printf("%d\n",*p1);

	printf("%d\n",*(p1+0));

	printf("%d %d\n",*(p1+0),p1[0]);			
//	printf("%d %d\n",*(p1+2),p1[2]);			
}

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

	getchar();
	
	return 0;
}

反汇编带如下:

13:       printf("%d\n",*p1);
0040BE68   mov         eax,dword ptr [ebp-4]
0040BE6B   movsx       ecx,byte ptr [eax]
0040BE6E   push        ecx
0040BE6F   push        offset string "%d\n" (0042001c)
0040BE74   call        printf (00401110)
0040BE79   add         esp,8
14:
15:       printf("%d\n",*(p1+0));
0040BE7C   mov         edx,dword ptr [ebp-4]
0040BE7F   movsx       eax,byte ptr [edx]
0040BE82   push        eax
0040BE83   push        offset string "%d\n" (0042001c)
0040BE88   call        printf (00401110)
0040BE8D   add         esp,8
16:
17:       printf("%d %d\n",*(p1+0),p1[0]);
0040BE90   mov         ecx,dword ptr [ebp-4]
0040BE93   movsx       edx,byte ptr [ecx]
0040BE96   push        edx
0040BE97   mov         eax,dword ptr [ebp-4]
0040BE9A   movsx       ecx,byte ptr [eax]
0040BE9D   push        ecx
0040BE9E   push        offset string "%d %d\n" (00420f80)
0040BEA3   call        printf (00401110)
0040BEA8   add         esp,0Ch

我们着重来分析一下printf("%d %d\n",*(p1+0),p1[0]);的反汇编代码

0040BE90   mov         ecx,dword ptr [ebp-4]
0040BE93   movsx       edx,byte ptr [ecx]

这二句汇编代码对应的是p1[0],还是*(p1+0),还是字符串参数呢?

之前我们讲过,C中编译器的函数调用方式默认为cdcal,前置知识如下:

  1. 平衡堆栈
    1. 调用者清理栈
  2. 参数压栈顺序
    1. 从右向左入栈

这里我们可以发现在printf函数前面,一共有3个push指令,而在调用完printf函数以后,esp加了0Ch,也就是3个参数。结合cdcal的函数调用方式。综上所述:

0040BE90   mov         ecx,dword ptr [ebp-4]
0040BE93   movsx       edx,byte ptr [ecx]
0040BE96   push        edx
#*p1[0] 

0040BE97   mov         eax,dword ptr [ebp-4]
0040BE9A   movsx       ecx,byte ptr [eax]
0040BE9D   push        ecx
#*(p1+0)

惊奇的发现,二个的汇编代码竟然一样?

得出结论

  1. *p == *(p1+0) == p1[0]

案例四:

#include "stdafx.h"

void Test()
{
	char* p1;		
	char** p2;		
	char*** p3;		
	char**** p4;		
	char***** p5;		
	char****** p6;		
	char******* p7;	
	
	printf("%d\n",*p1);

	printf("%d\n",*(p1+0));

	printf("%d %d\n",*(p1+0),p1[0]);			
	printf("%d %d\n",*(p1+2),p1[2]);			



}


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

	getchar();
	
	return 0;
}

反汇编代码如下:

18:       printf("%d %d\n",*(p1+2),p1[2]);
0040BEAB   mov         edx,dword ptr [ebp-4]
0040BEAE   movsx       eax,byte ptr [edx+2]
0040BEB2   push        eax
0040BEB3   mov         ecx,dword ptr [ebp-4]
0040BEB6   movsx       edx,byte ptr [ecx+2]
0040BEBA   push        edx
0040BEBB   push        offset string "%d %d\n" (00420f80)
0040BEC0   call        printf (00401110)
0040BEC5   add         esp,0Ch
  • 首先char*p1,是带一个 类型的变量,其次带 * 类型的变量+2时,需要去掉一个类型的宽度,所以这里是+2.
  • 而这里就对应着edx+2,首先是把p1所存储的值赋值给edx,然后加上2即可。

如果我这里写成* ( *(p1 + 2))可以吗?

不可以,我们之前讲过在变量前面加*之前,这个变量必须前面至少有一个 *。这里的p1是char *类型,所以在第一次加 *之后,此时的p1变为了char类型,此时前面是没有 *的,所以我们这里不能 *。

总结如下:
*(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]							
*() 与 []可以相互转换				

二十三、数组指针

数组指针不要与指针数组混为一体。

我们先来回忆一下指针数组:

案例一:

#include "stdafx.h"

void Test()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int* p1 = &arr[0];
	printf("%d %d\n",*p1,*(p1+1));
	
}


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

	getchar();
	
	return 0;
}

通过上面代码可以得知,* p1输出的值为1,*(p1 + 1)输出的值为2.

现在我们来了解数组指针,即int (*px)[2];

案例二:

#include "stdafx.h"

void Test()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int (*px)[2] = (int (*)[2])arr;

	printf("%d\n",sizeof(px));
}


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

	getchar();
	
	return 0;
}
//输出结果为4

我们先从带*类型的基本操作来了解,宽度、++、--、+整数、-整数、做比较

上面代码的输出结果为4,之前讲过任何带*类型的数据宽度都固定为4.

那么我们试着给px赋初值.

代码如下:

#include "stdafx.h"

void Test()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int (*px)[2] = (int (*)[2])100;

	printf("%d\n",px);
	px++;
	printf("%d\n",px);
}


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

	getchar();
	
	return 0;
}
//输出结果分别为100,108

可以看到,首先我们为px赋初值为100.首先打印了px,值为100,这是我们可以初步判定的。

可是为什么px++后,其值变成了108呢?

带*类型进行++或--,需要去掉一个 * 后的宽度.int( *px)[2],去掉一个 * 后为int px[2].

很多人碰到这个可能懵了,这不是int类型的数组吗?这样看是并不对的。这里的宽度应该是int的宽度*2 = 8

所以最后的px++后的值为108.

接下来让我们探测其对整数的加减。

代码如下:

#include "stdafx.h"

void Test()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int (*px)[2] = (int (*)[2])100;

	printf("%d\n",px);

	px = px + 2;
	printf("%d\n",px);
}


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

	getchar();
	
	return 0;
}
//输出结果为100和116

带*类型在进行加上或减去一个整数时,需要去掉一个本身一个 *的宽度去乘以整数。

这里px去掉一个*后,为int px[2],宽度为8.

最终的值为100 + 2 * 8 = 116

接下来让我们探测其最后的取值

代码如下:

#include "stdafx.h"

void Test()
{
	int arr[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
	int (*px)[2] = (int (*)[2])arr;

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


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

	getchar();
	
	return 0;
}
//打印的结果为7,7

为什么打印的结果为7呢?我们慢慢来分析

  • 首先px是一个数组指针,其指向的是arr数组的首地址。

  • 而px+1,是如何计算的呢?px是一个int (*px)[2]的指针,去掉一个 *后为int px[2].即宽度为8 * 1

  • 而arr数组是int类型的,也就是刚好8 / 4 =2,往右边移动二个数。

  • 计算完一个*后,此时的最后一个 * 指向的是arr数组,也就是我们熟悉的指针数组了,此时+4相当于宽度为4,乘以4为16,刚好往右边移动4个数。

所以最后的答案为7.而这里的打印结果一样,结合之前的多级指针,可以得出结论.* ( *(px+i) +m ) == px [i] [m]

那么问题来了,指针数组和数组指针有区别吗?换句话说,int * p[5] 与 int (*p)[5] 有什么区别?

答案是,有区别的,

  • [] 的优先级高于* 所以先组合成p[5]数组 再由int *说明 数组存储的类型 == int * p[5];
  • () 的优先级高于[] 所以*先p先组合成指针 再由int[5]说明数组的宽度
  • 优先级: **() > [] > ***

既然* ( *(px+i) +m ) == px [i] [m],那么是否 *(px + 1)[2] 等价于px [1] [2]?

我们先用一个例子试试,并用反汇编的角度解释这个问题

代码如下:

#include "stdafx.h"

void Test()
{
	int arr[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
	int (*px)[2] = (int(*)[2])arr;

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


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

	getchar();
	
	return 0;
}
//打印的结果为7和5

汇编代码如下:

5:        int arr[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
0040BE6E   mov         dword ptr [ebp-40h],1
0040BE75   mov         dword ptr [ebp-3Ch],2
0040BE7C   mov         dword ptr [ebp-38h],3
0040BE83   mov         dword ptr [ebp-34h],4
0040BE8A   mov         dword ptr [ebp-30h],5
0040BE91   mov         dword ptr [ebp-2Ch],6
0040BE98   mov         dword ptr [ebp-28h],7
0040BE9F   mov         dword ptr [ebp-24h],8
0040BEA6   mov         dword ptr [ebp-20h],9
0040BEAD   mov         dword ptr [ebp-1Ch],0Ah
0040BEB4   mov         dword ptr [ebp-18h],0Bh
0040BEBB   mov         dword ptr [ebp-14h],0Ch
0040BEC2   mov         dword ptr [ebp-10h],0Dh
0040BEC9   mov         dword ptr [ebp-0Ch],0Eh
0040BED0   mov         dword ptr [ebp-8],0Fh
0040BED7   mov         dword ptr [ebp-4],10h
6:        int (*px)[2] = (int(*)[2])arr;
0040BEDE   lea         eax,[ebp-40h]
0040BEE1   mov         dword ptr [ebp-44h],eax
7:
8:        printf("%d %d\n",*(px+1)[2],px[1][2]);
0040BEE4   mov         ecx,dword ptr [ebp-44h]
0040BEE7   mov         edx,dword ptr [ecx+10h]
0040BEEA   push        edx
0040BEEB   mov         eax,dword ptr [ebp-44h]
0040BEEE   mov         ecx,dword ptr [eax+18h]
0040BEF1   push        ecx
0040BEF2   push        offset string "%d %d\n" (00420f80)
0040BEF7   call        printf (00401110)
0040BEFC   add         esp,0Ch
9:    }

这里打印的结果为7和5,并不是一样的,虽然我们说过()和[],在带*类型中是可以进行互换的,但是这里的值并不一样,为什么呢?

首先优先级方面,大括号() > 中括号[] > *,所以在 *(px+1)[2]中。

  • 先计算的是px+1,即加上8 * 1.此时第二优先级为2,宽度为8 * 2.
  • 最终的宽度为24,除以4为6,即往右边移动6个数。
  • 最终答案为7.

二十四、数组指针拓展

一维数组指针

代码如下:

#include "stdafx.h"
#include <stdlib.h>

char code[] = {
	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 Function_1()
{
	//一维数组指针
	char (*p)[5] = (char (*)[5])code;//CHAR[5],如果++的话,所占字节为5
	printf("%#x\n",*(*(p+4)+3));
}

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

	system("pause");
	return 0;
}
//0x47

让我们来解析一下* (* (p + 4) + 3);

  • p本身的数据宽度为4,去掉一个* 后的宽度为char[5],即1 * 5.又因为需要+4,所以需要移动的地址为1 * 5 * 4 = 20
  • *(p+4)+3,原来的数据宽度为char *,去掉一个 * 后的宽度为char类型。最终宽度为1 *3 = 3
  • 最终的宽度为20 +3 = 23.又因为定义的数组指针是char类型。最终的值为0x47

二维数组指针

#include "stdafx.h"
#include <stdlib.h>

char code[] = {
	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 Function_2()
{
	//二维数组指针
	char (*px)[2][3];
	px = (char(*)[2][3])code;
	printf("%x\n",*(*(*(px+3)+2)+5));

	//变种
	/*
	int (*px)[2][3];
	px = (int(*)[2][3])code;
	printf("%x\n",*(*(*(px+2)+2)+2));*/

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

	system("pause");
	return 0;
}
//0x11

解析( * ((px+3)+2)+5)

  • px + 3:去掉一个*后,px为char[2] [3],宽度为2 * 3 * 1 == 6.又因为+3 ,最终要移动3 * 6 = 18
  • (px+3)+2:去掉一个 * 之前为char[2] [3]。去掉一个之后,为char[3].此时宽度为3 * 1 = 3.又因为+2,最终要移动3 * 2 =6
  • ( * (px+3)+2)+5):此时的数据类型为char [3].去掉一个*后宽度为char.最终的宽度为1.1 * 5 = 5
  • 18 + 6 + 5= 29.又因为是char类型,只需要一个字节即可。最终的答案为0x11.

下面我们来看一下int类型的数组指针。

#include "stdafx.h"
#include <stdlib.h>

char code[] = {
	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 Function_2()
{

	//变种
	int (*px)[2][3];
	px = (int(*)[2][3])code;
	printf("%#x\n",*(*(*(px+2)+2)+2));

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

	system("pause");
	return 0;
}
//输出结果为0x64000000

解析*( *( *(px+2)+2)+2):

  • px + 2:去掉一个*后的px宽度为int[2] [3].本身的宽度为2 * 3 * 4 == 24.又因为 + 2,所以最终为24 * 2 = 48
  • *(px+2)+2:去掉一个 *后的px宽度为一个一维数组int[3].此时的宽度为3 * 4 ==12.又因为 +2.最终为12 * 2 = 24
  • *( *(px+2)+2)+2:去掉一个 * 后的px宽度为int类型,为4,又因为+2,最终为4 * 2 = 8
  • 最终为48 + 24 + 8 = 80.这里的数组指针为int类型,查到了对应的偏移之后,要查询4个字节
  • 最终的结果为0x64000000

三维数组指针:

代码如下:

#include "stdafx.h"
#include <stdlib.h>

char code[] = {
	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 Function_3()
{
	char (*px)[2][3][2];
	px = (char (*)[2][3][2])code;
	printf("%x\n",*(*(*(*(px+2)+2)+2)+2));
}



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

	system("pause");
	return 0;
}
//0x64

解析:*( *( *( *(px+2)+2)+2)+2)

  • px + 2:去掉一个*后的自身宽度为char[2] [3] [2],宽度为2 * 3 * 2 * 1== 12.又因为+2,最终为12 * 2 = 24
  • *(px+2)+2:去掉一个 *后为二维数组char [3] [2],此时的宽度为3 * 2 * 1 = 6.又因为+2,最终为6 * 2 = 12
  • *( *(px+2)+2)+2:去掉一个 * 后为一维数组char[2],此时的宽度为2 * 1 == 2,又因为+2,最终为2 * 2 = 4
  • *( *( *(px+2)+2)+2)+2:去掉一个 * 后的宽度为char,此时的宽度为1,又因为+2,最终为2
  • 24 + 12 + 4 + 2 = 42
  • 又因为是char类型,最终的结果为0x64

PS:并不是一维数组指针只能操作一维数组,一维数组指针也可以操作,二维数组,三维数组,甚至多维数组。但是我们要明白一个道理,不同指针操作不同的数组是有宽度存在的。譬如char (p)[2].其本身虽然为4,但是去掉一个 后,此时的宽度为char[2],即宽度为2 * 1 * 4 = 8.同理如果是char (p)[2] [4].此时去掉一个 * 的 宽度后,为char [2] [4].此时的宽度为2 * 4 * 1 =8.如果是int (px) [2] [4]的话,去掉一个 * 后的宽度为2 * 4 * 4 = 32.所以我们要根据不同的指针的不同类型去确定宽度。

一维数组指针访问一维数组

代码如下:

#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));	
		
}		


int main(int argc,char* argv[])
{
	Function_1_1();
	system("pause");
	return 0;
}
//输出结果 5 5 5

这里的结果是通过宽度以及指针的数据类型来确定的。譬如我的宽度为16,访问的一维数组为int类型,那么我只需要往后访问4个数即可。

解析:*( *(p)+4):

  • 宽度为: 4 * 5 * 0 + 4 * 4 = 16
  • 又因为是int类型,最终的结果为5

解析:*( *( px+1)+2)

  • 宽度为:2 * 4 * 1 + 4 * 2 = 16
  • 又因为是int类型,最终的结果为5

解析:*( *(px+2)+0)

  • 宽度为:2 * 4 * 2 + 4 * 0 =16
  • 又因为是int类型,最终的结果为5

一维数组指针访问二维数组

代码如下:

#include "stdafx.h"
#include <stdlib.h>

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));	
}		



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

	system("pause");
	return 0;
}
// 5 10 5 7

解析:

*( *(p)+4):

  • 宽度为4 * 5 * 0 + 4 * 4 = 16
  • 又因为是int类型的指针,最终的结果是5

*( *( p+1)+4):

  • 宽度为:4 * 5 * 1 + 4 * 4 =36
  • 因为是int类型的指针,最终的结果是10

*( *( px)+4):

  • 宽度为:2 * 4 * 0 + 4 * 4 = 16
  • 因为是int类型的指针,最终的结果是5

*( *(px+1)+4):

  • 宽度为: 2 * 4 * 1 + 4 * 4 = 24
  • 因为是int类型的指针,最终的结果是7

一维数组指针访问三维数组

代码如下:

#include "stdafx.h"
#include <stdlib.h>

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));	
}		



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

	system("pause");
	return 0;
}

这里要注意的是,数组里面因为并没有全部赋值,所以某些位置会补0

解析:

*( *(p)+4):

  • 宽度4 * 5 * 0 + 4 * 4 = 16
  • 最终的结果为:5

*( *(p+1)+4):

  • 宽度为:5 * 4 * 1 + 4 * 4 = 36
  • 最终的结果为:0

*( *(p+2)+2):

  • 宽度为:5 * 4 *2 + 4 * 2 =48
  • 最终的结果为:11

二维数组指针访问一维数组

代码如下:

#include "stdafx.h"
#include <stdlib.h>

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]);	
}		




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

	system("pause");
	return 0;
}
//输出结果:1,7,8,13

解析:

*( *( *(p))):

  • 宽度为:2 * 2 * 4 * 0 + 2 * 4 * 0 + 4 * 0 = 0
  • 最终的值为:1

*( *( *(p+1)+1)):

  • 宽度为:2 * 2 * 4 * 1 + 2 * 4 * 1 + 4 * 0 = 24
  • 最终的值为:7

*( *( *(p+1)+1)+1):

  • 宽度为:2 * 2 * 4 *1 + 2 * 4 * 1 + 4 * 1 = 28
  • 最终的值为:8

*( *( *(p+2)+1)+2):

  • 宽度为:2 * 2 * 4 * 2 + 2 * 4 * 1 + 4 * 2 = 48
  • 最终的值为:13

二维数组指针访问二维数组

代码如下:

#include "stdafx.h"
#include <stdlib.h>

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 main(int argc,char* argv[])
{
	Function_2_2();
	

	system("pause");
	return 0;
}
//最终的结果分别为1、7、8、24.

解析:

*( *( *(p))):

  • 宽度为: 2 * 2 * 4 * 0 + 2 * 4 * 0 + 4 * 0 = 0
  • 最终的值为:1

*( *( *(p+1)+1)):

  • 宽度为: 2 * 2 * 4 * 1 + 2 * 4 * 1 + 4 * 0 = 24
  • 最终的值为:7

*( *( *(p+1)+1)+1):

  • 宽度为:2 * 2 * 4 * 1 + 2 * 4 * 1 + 4 * 1 = 28
  • 最终的值为:8

*( *( *(p+3)+2)+2):

  • 宽度为:2 * 2 * 4 * 3 + 2 * 4 * 2 + 4 * 2 = 48 + 16 + 8 = 72
  • 最终的值为:24

二十五、函数指针

首先我们来了解一下函数指针的声明:

  • 返回类型 (* 函数名) (参数表)
  • int (*pFun) (int,int)

对应的,这里的第一个int就是这个函数指针的返回类型,第二三个int表示的是这个函数指针所对应的参数表。pFun则是函数名。

下面,我们来探测一下函数指针的相关参数

#include "stdafx.h"
#include <stdlib.h>


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


	int (*pFun)(int,int);
	
	printf("%d\n",sizeof(pFun));
	//探测,宽度,++,--,整数,相减,差值



	system("pause");
	return 0;
}
//输出的结果为4

初步判断,函数指针的宽度为4.接下来我们试着探测其他参数。

函数指针的++、--.

在此之前,我们需要对这个函数指针进行赋值,当前别忘记了强制转换函数指针的类型。


#include "stdafx.h"
#include <stdlib.h>

char code[] = {
	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 x = 10;

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

unsigned char arr[] = 
{
	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 (*pFun)(int,int);
	
	//探测,宽度,++,--,整数,相减,差值
	pFun = (int (__cdecl *)(int,int))10;
	printf("%d\n",pFun);
	pFun++;
	printf("%d\n",pFun);



	system("pause");
	return 0;
}
//pFun++无法编译成功

虽然这个函数指针本身的类型为(__cdecl *),也就是4.但是去掉一个 * 后的类型,我们能具体进行判断(int __cdecl (int,int))是占多少字节宽度吗?

答案显然是不能的,C语言中其实,不仅代码是数据,函数其实也是数据.

从上图中可以看出,函数其实也是由一大堆数据组成的,即硬编码

所以这里函数指针去掉一个*后的宽度,我们是不能确定的,所以它不能进行++、--、甚至整数、相减、甚至差值的操作。

既然这里函数指针定义的是一个返回类型为Int,参数列表为二个int。那么我们也试着把一个返回类型为int,参数列表为2个int类型的函数地址给函数指针。

代码如下:


#include "stdafx.h"
#include <stdlib.h>


int x = 10;

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



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

	int (*pFun)(int,int);
	
	//探测,宽度,++,--,整数,相减,差值
	pFun = Function;



	system("pause");
	return 0;
}
//正常编译

所以这里编译器会把Function进行一个处理,知晓Function函数是一个int作为返回类型,参数列表为2个int的函数。

既然我们能把Function函数的地址传给函数指针,我们是否能通过函数指针来调用函数呢?

#include "stdafx.h"
#include <stdlib.h>


int x = 10;

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



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

	int (*pFun)(int,int);
	
	//探测,宽度,++,--,整数,相减,差值
	pFun = Function;
	int x = Function(1,2);
	int y = pFun(1,2);
	printf("%d %d\n",x,y);

	system("pause");
	return 0;
}
/*
输出的结果都为3.

*/

所以我们可以得出结论,把某一个函数赋值给一个函数指针,函数指针的用法跟正常的函数调用没什么区别。

代码和数据有什么区别?

刚刚我们试验了一下,Function函数名其实在内存中也就是一些数据。代码其实也是数据,存储到代码区。

所以代码和数据其实没什么区别。

隐藏代码到数据区

下面是一段调用函数的代码。


#include "stdafx.h"
#include <stdlib.h>


int x = 10;

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



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

	int (*pFun)(int,int);
	int x = Function(1,2);
	

	system("pause");
	return 0;
}
Function:
00401010 55                   push        ebp
00401011 8B EC                mov         ebp,esp
00401013 83 EC 40             sub         esp,40h
00401016 53                   push        ebx
00401017 56                   push        esi
00401018 57                   push        edi
00401019 8D 7D C0             lea         edi,[ebp-40h]
0040101C B9 10 00 00 00       mov         ecx,10h
00401021 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
00401026 F3 AB                rep stos    dword ptr [edi]
00401028 8B 45 08             mov         eax,dword ptr [ebp+8]
0040102B 03 45 0C             add         eax,dword ptr [ebp+0Ch]
0040102E 5F                   pop         edi
0040102F 5E                   pop         esi
00401030 5B                   pop         ebx
00401031 8B E5                mov         esp,ebp
00401033 5D                   pop         ebp
00401034 C3                   ret

从上面的Function反汇编来看,存在着55,8B,EC,83,EC,40的十六进制数。这个数其实就是硬编码。

我们将这些数据(硬编码)单独提取出来,先存入到一个数组中。

我们是否可以把函数指针应用到这上面呢?

当然可以,既然这些硬编码是从刚刚Function函数的反汇编中提取出来的,我们可以通过函数指针的方式来操作它。


#include "stdafx.h"
#include <stdlib.h>


unsigned char arr[] = 
{
	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 (*p)(int,int);
	p = (int (__cdecl *)(int,int))&arr;
	int x = p(1,2);
	int y = p(3,4);
	printf("%d %d\n",x,y);

	

	system("pause");
	return 0;
}
/*
输出的结果为
3,7

*/

可以看到我们把arr作为一个函数地址传入给函数指针p。输出的结果为3和7.成功实现了Function函数的功能。

函数指针的另一种表示

  • typedef int (*Fun)(int,int);
    • 这个不是变量的声明,而是为函数指针起个别名:Fun 相当于函数指针类型
  • Fun p;
    • 这个才是变量的声明,p是变量,Fun是类型.

二十六、函数指针和数组指针练习

将一个函数存储到数据区,通过指针进行访问.

#include "stdafx.h"
#include <stdlib.h>


unsigned char arr[] = 
{
	0x55,
	0x8B, 0xEC,
	0x83, 0xEC, 0x48,
	0x53,
	0x56,
	0x57,
	0x8D ,0x7D, 0xB8,
	0xB9 ,0x12 ,0x00 ,0x00, 0x00,
	0xB8, 0xCC, 0xCC, 0xCC ,0xCC,
	0xF3, 0xAB,
	0xC7, 0x45, 0xFC, 0x00, 0x00, 0x00, 0x00,
	0xC7, 0x45, 0xF8, 0x00, 0x00, 0x00, 0x00,
	0xEB, 0x09,
	0x8B, 0x45, 0xF8,
	0x83, 0xC0, 0x01,
	0x89, 0x45, 0xF8,
	0x83, 0x7D, 0xF8, 0x64,
	0x7D, 0x10,
	0x8B, 0x4D, 0x08,
	0x03, 0x4D, 0x0C,
	0x8B, 0x55, 0xFC,
	0x03, 0xD1,
	0x89, 0x55, 0xFC,
	0xEB, 0xE1,
	0x8B, 0x45, 0xFC,
	0x5F,
	0x5E,
	0x5B,
	0x8B, 0xE5,
	0x5D,
	0xC3
};


int Function(int x,int y)
{
	int sum = 0;
	for(int i = 0;i<100;i++)
	{
		sum = sum + (x + y);
	}
	return sum;
}

int main(int argc,char* argv[])
{
	int (*p)(int,int);
	p = (int (__cdecl *) (int ,int ))&arr;
	//Function(1,2);
	int x = p(1,2);
	printf("%d\n",x);

	

	system("pause");
	return 0;
}
//结果300

这里是把Function的反汇编代码的硬编码,手搓下来,然后赋给arr数组,然后通过函数指针调用即可。结果为300

3、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];		

答案分别是:

  1. 03020100
  2. 20000907
  3. 00001100
  4. 00000001