一 内存与指针
1、 内存是什么?
从硬件形态上说,内存就是一条形物理设备,从功能上讲,内存是一个数据仓库,程序内在执行前都要被装载到内存中,才能被中央处理器执行。
内存是由按顺序编号的一系列存储单元组成的,在内存中,每个存储单元都由唯一的地址,通过地址可以方便地在内存单元中存储信息。
在计算机中,一切信息都是以二进制数据的形式体现的,每个内存单元的容量是1B,即8bit(8个0、1二进制位)。
2、内存与CPU读写速度快,断电就没有了,容量比较小,成本比较高,高级服务器,读写很频繁的文件全部存放内存
3、把一个函数名通过%x打印出main的首地址
4、一个程序载入内存,代码数据都有地址,外挂就是调用函数,修改数据
而函数就是代码,变量就是数据
5、32位的计算机最多的内存容量是4G
6、变量名就是对内存一段空间里面数据的抽象
指针变量可以指向任何的一个变量
int x=10;
int *p=&x; //p是一个指针变量,p可以是任何变量的地址
外挂强制修改(用dllinject注射)
二 指针详解
1、指针变量在使用之前必须进行初始化。
int num=100;
int *p;
p=# //非法,可以编译,运行报错,会把100当做一个地址
2、指针只是一个地址,大小是固定的,就是四个字节。
int *p1;
double *p2;
char *p3;
sizeof(p1); //结果为 4
sizeof(p2); //结果为4
sizeof(p3); //结果为4
3、 指针和地址的区别
两个要点:一、指针是个量,对应着一块内存区域,二,指针存储的信息是某个内存单元的地址。
比如: int num=10;
int *p=#
//&num 是一个地址,是一个常量
//而p是一个指针变量,可以存储一个地址
比如300500是一个地址,
int *p=(int *)300500 是一个指针,p存储的是地址,指针有类型,从哪里开始,长度是多少,从哪里结束,得知了类型以后,就知道这片内存数据是如何解析
4、指针变量的声明与初始化
在声明一个指针后,编译器并不会自动完成其初始化,此时,指针的值是不确定的,也就是说,该指针指向那块内存单元是完全随机的。
如果在指针变量声明之初确实不知道该将此指针指向何处,最简单的方式是将其置“0”,C语言中提供了关键字NULL
其基本形式为:
类型* 指针变量名;
int *pNum=NULL;
值为NULL的指针称为空指针,这意味着,指针并不指向任何地址。
在头文件 stdio.h 中,NULL 定义为常量。
三 间接访问和直接访问
1、取地址运算符&, 间接运算符*
&运算符: 取地址运算符,&m即是变量m在内存中的实际地址。
*运算符: 指针运算符 (通常称为间接引用运算符),它返回其操作数 (即一个指针)所指向的对象的值.
如图所示:
&num 是直接访问变量num的地址,而 (&num)中则是间接访问
2、 直接访问: 按变量地址存取变量值
间接访问: 通过存放变量地址的变量去访问变量
定义指针变量p, &num 直接访问num 的地址,*p通过num的地址间接的访问变量num
四 打印指针地址
1、地址格式符:%p %x
%x: 按照十六进制打印,无意义的0就不打印
%p: 显示地址的位数,32位,8个十六进制,2^4=16,32位
显示地址的位数,64位,16个十六进制位,64个二进制位
int num=10;
int *p=#
printf("%x,%x",p,&num);
printf("\n%p,%p",p,&num);
打印的格式:
32为的情况下:
64位的情况下:
五 scanf初始化指针
指针不可以乱指,否则会程序崩溃
六 指针与函数参数
c语言要改变外部变量只有传地址,java,c++有引用,c没有
数组当做参数的时候,传递的是指针
数组作为参数的时候,改变的是原来的数组
七 指向指针的指针(二级指针)
作用
1、 指针变量也是变量,占据一定的内存空间,有地址,因此可以用一个指针指向它,这称为指向指针的指针,或二级指针。
2、 函数形式参数,除了数组以外,传递的任何数据,变量,都会新建一个变量接收传入的变量的值。不影响原来的变量,如果是一个数据,传递数据的地址(指针),如果是一个指针,传递指针的地址。
db是double类型的变量。
运行结果:
可以发现main函数的 p的地址和 change函数的 p的地址不相同,
而是新建一个变量接收传入的变量的值
3、 *pp=&bd 则改变了指针的指向,**pp=bd
4、二级指针在外挂应用较多
编写一个外挂程序,通过二级指针改变一级指针的指向。
程序的部分代码
运行生成一个exe程序,打印出各个变量的地址,每3秒打印一次。
要向上面这个程序中注入dll。通过二级指针改变一级指针的指向来改变
创建外挂:需要建模块,模块不需要main函数
新建项目,右键属性将配置类型改为动态库(dll)。 将目标文件名改为
goA
需要用到_declspec(dllexport) dll函数导出 接口
需要改的是指针那么就需要用到二级指针
打印的结果
类型不匹配有时会出现偶然的成功现象
但大多数情况是失败的
八 指针的类型和指针所指向的类型
1、 所谓指针类型,指的是声明指针变量时位于变量名前的“类型*”,而所谓指针所指向的类型,指的是为指针初始化或赋值的变量类型。
2、 不是同一类型的指针,不可以任意赋值。
不同的数据类型,大小不一样(如果强制赋值的话,就会少读取或多读取,内存有很多垃圾0,1),解析方式不一样
(1) p1、px 是同一类型的指针,解析px并没有什么错误
(2) p1、p2 不是同一类型的指针,此时,打印出p2则并不是A.
3、 同类型指针的赋值
这是最常见的一种情况,如所示,pN1和pN2是两个相同类型的指针,执行“pN2=pN1;”这样一个赋值操作后,pN1和pN2指向同样的地址,也就是说,两个指针指向同一个内存单元,对pN2的任何改动都会影响pN1的值,反之亦然。
九 指针的类型和指针所指向的类型不同
1、 指针除了地址以外还有类型,类型决定了指向的数据大小,决定了数据的解析方式。指针的类型必须要与指针指向的类型一致,一定会出现偏差。即使地址相同,大小相同。
2、 (1)指向内存字节数大于指针类型占据的字节数
指针的类型必须要与指针指向的类型一致,不一致,大小不一样,解析方式不一样。
指针的类型double的大于指针指向的类型int
运行结果:
2)指向内存字节数小于指针类型占据的字节数
指针的类型int的小于指针指向的类型double
运行结果:
十 指针变量的值
1、 指针变量的值:
是指针本身存储的数值,这个值将被编译器当作一个地址,而不是
一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址长度都为32位。
指针所指向的内存区:
就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。
指针存储的是地址,地址是首地址,从哪里开始,从哪里结束,由类型决定,
类型决定长度,决定如何解析。
2、自己编写外挂改变,通过另外一个程序改变这个进程。
在上面的代码中添加如下代码,使其打印数据:
while(1)
{
printf("我的级别是%c,我的年龄是%d,我的人民币是%fW",ch,num,db);
Sleep(5000);
}
要注入的dll程序
通过指针直接访问变量的地址,使其通过注入的方式,从而改变它的值,从而改变另一个程序的运行结果