5.0.1 外挂原理与指针
什么是内存?
从硬件形态上来说,内存就是一条形物理设备,从功能上来讲,内存是一个数据仓库,程序在执行前都要被装载到内存中,才能被中央处理器执行。
内存是由按顺序编号的一系列存储单元组成的,在内存中,每个存储单元都有唯一的地址,通过地址可以方便地在内存单元中存储信息。
在计算机中,一切信息都是以二进制数据的形式体现的,每个内存单元的容量是 1B,即 8Bit(8个0、1二进制位)。
内存与CPU读写速度快,断电就没有了,容量比较小,成本比较搞,高级服务器,读写很频繁的文件全部存放内存,顶级黑客的电脑是没有硬盘的。
变量名是什么?
概念:既能读又能写的内存对象,称为变量;若一旦初始化后不能修改的对象则称之为常量。
变量定义形式:类型,标识符,标识符,……,标识符;
变量名就是对内存一段空间里面的数据的抽象。
int a = 10; double b = 20;
例1:查看内存中定义的变量的地址
```c
#include
#include
void main() {
// a其实的地址的别名
int a = 10;
printf(“%p\n”, &a);
变量的本质
程序通过变量来申请和命名内存空间 int a =0;
通过变量名访问内存空间。
结论:
变量:一段连续内存空间的别名。
数据类型和变量的关系,通过通过数据类型定义变量。
一个程序载入内存,代码数据都有地址,外挂就是调用函数,修改数据。而函数就是代码,变量就是数据。
总结:
对内存,可读可写。
通过变量往内存读写数据。
不是向变量读写数据,而是向变量所代表的内存空间中写数据。
名字就是变量,地址是身份证,外号是引用。姓名可以看作是身份证号的别名。
5.0.2 指针的基本语法
指针的作用:
操作内存
注意:地址就是你的身份证号,变量就是你的名字
举例:唯一真名
*号和&号的意思:
取地址运算符&,间接运算符
&运算符:取地址运算符,&m 即是变量m在内存中的实际地址。
运算符:指针运算符(通常称为间接引用运算符)它返回其操作数(即一个指针)所指向的对象的值。
指针的大小是固定的,指针的大小和类型无关,指针的类型与步长(+1后所得到的值,指针的步长和指针数组有很大联系)有关。无论什么类型下指针变量的类型永远是: 64bit环境下是8字节 32bit环境下是4字节
需要注意的是:char*p = &H;它的类型要保持和 char H = ‘A’;的一致否则会出现对溢出内存的赋值。
例:打印一个值的地址:
```c
#include
#include
void main() {
int loser = 38;
printf(“%p\n”, &loser); // &取地址,返回其变量真实地址
指针变量语法
其基本形式为:
类型 指针变量名;
```c
int b = 10;
int a = &b;
int *p = NULL;
p = &b;<br />需要注意的是指针和指针变量是两种概念。指针指的是地址,指针变量指的是存放指针的变量。还需要注意指针变量需要初始化。<br />
c
#include
#include
void main() {
int loser = 38;
printf(“%p\n”, &loser); // &取地址,返回其变量真实地址
int p = &loser; // 声明一个p,它现在就是一个指针变量,它存储的是loser的内存地址,这样是声明与赋值二合一
p = 3800; // 修改地址的值为:3800。我们通过间接的修改了loser的值
printf(“%d\n”, loser); // 现在loser的值为:3800
/ 通常声明可以采用这样的形式,但必须要初始化 /
int pa = NULL; //如果不初始化会出现问题
pa = &loser; // pa获取loser的内存地址
pa = 521; // 然后我们可以通过p(通过地址)去修改loser的值。通过来间接操作内存来修改变量的值
printf(“%d\n”, loser);
// 总结:指针变量是一种变量,它可以存储任意类型的地址。*是用来操作地址的,&是变量的的地址
system(“pause”);
}<br />总结:<br />指针变量是一种变量,它可以存储任意类型的地址。*是用来操作地址的,&是变量的的地址。<br />*就是间接引用运算符,它就是通过地址间接去操作变量的值。就是通过地址找内存。<br />例如:<br />网名:小帅哥 ID账号:123456<br />&就是直接去拿ID账号,网名就是变量名。<br />我们添加好友用ID账号。<br />*就相当于通过ID账号查找内存进而操控。<br />注意:*变量就是指针变量,指针是一种概念。<br />_如果还是不懂 * 和 & 就另请大佬_<br /> <br />指针的详解<br />
c
#include
#include
void main() {
int a = 250;
(&a) = 38; // 这个实际上和 a = 38 一样,但是这个是通过指针的方式来实现的。操作(&a)变量地址的值 = 38
printf(“%d\n”, a);
int p = NULL; // 此处没有任何操作,只是声明和初始化一个指针变量
p = &a;
printf(“%p\n”, p); // p 在这里存储的是a的内存地址
p = 200; // p加上一个*号我们就可以修改a的值(通过内存地址修改)
printf(“%d\n”, a);
system(“pause”);
}<br />指针练习一<br />需要注意的是:char*p = &H;它的类型要保持和 char H = 'A';的一致否则会出现对溢出内存的赋值。<br />如果将char *p = &H; 改成 int* p = &H';<br />int* 它指向的是整型空间,占4个字节,而指向的 H 只是一个字符型,占1个字节,那么*p = 'B'; 是错误的根据,它会导致对溢出空间的赋值。会报错如:_run-time check failure #2 - stack around the variable 'hello' was corrupted._<br /> <br />
c
#include
#include
/*
假设我知道了你的账号(内存地址),我要修改你的网名(内存中的内容)
*/
void main() {
char H = ‘A’;
char p = &H; // 指针变量就是存地址的变量
p = ‘B’;
printf(“H的值为:%c\n”, H);
printf(“p存储的地址为:%p\n”, p);
printf(“p的地址为:%p\n”, &p); // 指针变量的地址,好比黑客的号被盗了
// 黑客盗了 H的账号,有人盗了黑客的账号,能不能通过黑客的号找到 H 的号 :答案是成立的
// 能不能通过黑客的账号去修改 H 的网名 :可以的,采用二级指针
// 注意:存储指针变量的地址只能是二级指针
H = ‘C’;
printf(“H=’C’;的值为:%c\n”, H);
printf(“通过地址获取的为:%c\n”, *p); // 通过地址去找内容
system(“pause”);
}<br />指针练习二<br />
c
#include
#include
void main() {
int a = 10;
int p = NULL;
p = &a;
p = 100;
printf(“a = %d\n”, a);
char H = NULL;
double D = NULL;
printf(“指针p的大小为:%d\n”, sizeof(p));
printf(“指针H的大小为:%d\n”, sizeof(H));
printf(“指针D的大小为:%d\n”, sizeof(D));
// 总结:无论什么类型下指针变量的类型永远是: 64bit环境下是8字节 32bit环境下是4字节
指针的变量和指针的概念(重点)
“指针”是概念,“指针变量”是具体的实现,指针也是一个变量,所以需要进行定义,而对指针的定义,与一般变量一样。
指针和指针变量的关系
指针就是地址,地址就是指针。
地址就是内存单元的编号。
指针变量就是存放指针地址的变量。
指针和指针变量是两个不同的概念,但要注意的是,通常我们叙述时会把指针变量简称为指针,实际他们的含义是不同的。
指针变量也是一种变量,不同的是,指针变量只存储地址。
指针变量既然是变量那么它的存储地址是可以改变的。不是固定的。
注意区分指针变量的值和指针变量的地址。
5.0.3 声明指针需要注意的点
指针变量在使用之前必须进行初始化。c<br />int num = 100;<br />int *p;<br />p=#<br />
上面演示的代码是非法的,可以编译,运行报错,会把100当作一个地址。
指针只是一个地址,大小是固定的,在32bit环境下是4字节,在64bit下是8字节。
指针和地址的区别两个要点:
指针是个量,对应着一块内存区域
指针存储的信息是某个内存单元的地址
比如:c<br />int a = 10;<br />int *p = &a;<br />
&a 是一个地址,是一个常量。而p是一个指针变量,可以存储一个地址。比如:300500是一个地址,int p=(int)300500是一个指针变量。p存储的是地址,指针有类型,从哪里开始,长度是多少,从哪里结束,得知了类型以后,就知道这片内存数据是如何解析。
指针变量的声明与初始化:在声明一个指针后,编译器并不会自动完成其初始化,此时指针的值是不确定的,也就是说,该指针指向那块内存单元是完全随机的。
如果在指针变量声明之初确实不知道该将此指针指向何处,最简单的方式是将其置为”0”,C语言中提供了关键字 NULL。
5.0.4 数组和指针(简单)
数组名是数组的首元素的地址,同时也是数组地址。
例如:
```c
#include
#include
void main() {
int a[3] = { 1,2,3 }; // 1值的地址和取数组的地址是一致的a[3]。{1,2,3}他们都要一个自己独立的内存地址
/*
例如:白素贞 - 赵雅芝
赵雅芝是白素贞的代表,但不能说赵雅芝就是白素贞。
可以扮演白素贞的演员很多,说起白素贞就想起赵雅芝。
*/
char bai[] = { ‘z’,’l’,’j’ };
printf(“白素贞: %p\n”, &bai);
printf(“赵雅芝: %p\n”, bai); // 打印数组名,默认是打印数组首元素的地址
printf(“数组首元素:%p\n”, &bai[0]); // 此句与printf(“赵雅芝:%p\n”, bai);功能一样
/*
白素贞是个角色,谁都可以演:赵雅芝、周涛、菊花
赵雅芝就是白素贞这个数组的第一个元素(首元素)
它的地址就是数组的地址(一维数组下)
*/
system(“pause”);
}<br />总结:<br />数组名就是表示数组首地址常量<br />数组地址和数组首元素地址不同之处在于类型不一样。<br />类型不一样导致步长不一样。<br />例如:<br />
c
#include
#include
void main() {
int b = 10; // 分配4字节的内存
int array[10] = { 1,2,3,4,5,6,7,8,9,10 }; // 告诉编译器分配410 = 40个字节的内存
printf(“array:%p\narray+1:%p\narray:%p\n&array+1:%p\n”, array, array + 1, &array, &array + 1);
printf(“array:%d,array+1:%d,array:%d,&array+1:%d\n”, array, array + 1, &array, &array + 1);
/**
array+1 和 &array+1 的结果是不同的
是因为 array 和 &array 所代表的数据类型不一样
array 代表数组首元素的地址 如:代表赵雅芝这个演员
&array 代表整体数组的地址 如:代表白素贞这个角色
这里array也可以写成 array[0] 是一个int类型 +1 = 加了4字节 它+1等于加了一个int型变量
&array 是数组类型+1后 = 加了40字节 它+1等于加了一个数组
虽然地址相同,但+1后的值不同。因为他们的类型不一样
array 和 array[0]是等同的
/
5.0.5 判断数据类型的大小
```c
#include
#include
void main() {
int a = 10;
int b[10];
printf(“sizeof(a):%d\n”, sizeof(a)); // 4字节
printf(“sizeof(int ):%d\n”, sizeof(int)); // 32Bit下4四字节,64Bit下8字节
printf(“sizeof(b):%d\n”, sizeof(b)); // 410 = 40字节
printf(“sizeof(b[0]):%d\n”, sizeof(b[0])); // 4字节
printf(“sizeof(b):%d\n”, sizeof(*b)); // 4个字节(取地址)
5.0.6 修改变量的两种方式
变量的本质:
程序通过变量来申请和命名内存空间。
通过变量名访问内存空间。
变量:一段连续内存空间的别名。
修改变量的两种方式:
直接:通过变量名直接修改变量。
间接:通过指针地址来间接修改变量。
```c
#include
#include
/*
采用两种方式来修改变量:直接与间接(提供指针)
*/
void main() {
int a = 10; // 变量名可以看作内存地址的别名
a = 1; // 直接修改
printf(“a = %d\n”, a);
int* p = &a; // 变量地址相当于身份证号,拿取 a 的内存地址
printf(“p = %p &a = %p\n”, p, &a);
*p = 100; // 间接 提供内存地址间接修改变量
printf(“a = %d\n”, a);
printf(“\n”);
system(“pause”);
}
```
5.0.7 数据类型的本质
数据类型可理解为创建变量的模具:是固定内存大小的别名。
数据类型的作用:编译器预算对象(变量)分配的内存空间的大小。
注意:数据类型只是模具,编译器并没有分配空间,只有根据类型(模具)创建变量(实物),编译器才会分配空间。
数据类型和变量的关系:通过数据类型定义变量。