1、内存是由 Key 和 Value 组成,Key 是内存地址、Value 是存储的数据;
2、Key:是一个32位长度的二进制数;(64位的程序则是64位长度的二进制)
3、Value:则是一个8位长度的二进制数;(所以说计算机只能存储 0 和 1 就是这原因)
4、内存组成结构如下:
1、指针是用于指向一个值类型数据,非想象中的面向过程逻辑、认为第一个读取后会自动指向下一个,哈哈;
2、如 int 类型的指针,就是将指定内存地址中的数据转换成 int 数据;
3、由于 int 类型长度为32位(4byte),所以指针读取数据时会自动取连续4byte的数据来转换成 int;
OK,说完原理得开始说代码了,来个华丽的分割线;
再声明一下:
int val = 10; unsafe { int* p = &val; //&val用于获取val变量的内存地址,*p为int类型指针、用于间接访问val变量 *p *= *p; //通过指针修改变量值(执行此操作后 val 变量值将会变成 100) }
string val = "ABC"; unsafe { fixed (char* p = val) //fixed用于禁止垃圾回收器重定向可移动的变量,可理解为锁定引用类型对象 { *p = 'D'; //通过指针修改变量值(执行此操作后 val 变量值将会变成 "DBC") p[2] = 'E'; //通过指针修改变量值(执行此操作后 val 变量值将会变成 "DBE") int* p2 = (int*)p; //将char类型的指针转换成int类型的指针 } }
double[] array = { 0.1, 1.5, 2.3 }; unsafe { fixed (double* p = &array[2]) { *p = 0.2; //通过指针修改变量值(执行此操作后 array 变量值将会变成{ 0.1, 1.5, 0.2 }) } }
User val = new User() { age = 25 }; unsafe { fixed (int* p = &val.age) //fixed用于禁止垃圾回收器重定向可移动的变量,可理解为锁定引用类型对象 { *p = *p + 1; //通过指针修改变量值(执行此操作后 val.age 变量值将会变成 26) } } /* public class User { public string name; public int age; } */
char val = 'A'; unsafe { int valAdd = (int)&val; //获取val变量的内存地址,并将地址转换成十进制数 //IntPtr address = (IntPtr)123; //选择一个内存地址(可以是任何一个变量的内存地址) IntPtr address = (IntPtr)valAdd; //选择一个内存地址(暂使用val变量的内存地址做测试) byte* p = (byte*)address; //将指定的内存地址转换成byte类型的指针(如果指定的内存地址不可操的话、那操作时则会报异常“尝试读取或写入受保护的内存。这通常指示其他内存已损坏。”) byte* p2 = (byte*)2147483647; //还可通过十进制的方式选择内存地址 byte* p3 = (byte*)0x7fffffff; //还可通过十六进制的方式选择内存地址 *p = (byte)'B'; //通过指针修改变量值(执行此操作后 val 变量值将会变成 'B') }
int valInt = 10; //定义一个int类型的测试val char valChar = 'A'; //定义一个char类型的测试val int* pInt = &valInt; //定义一个int*类型的指针 char* pChar = &valChar; //定义一个char*类型的指针 void* p1 = pInt; //void*可以用于存储任意类型的指针 void* p2 = pChar; //void*可以用于存储任意类型的指针 pInt = (int*)p2; //将void*指针转换成int*类型的指针 (#需要注意一点:因为都是byte数据、所以不会报转换失败异常) pChar = (char*)p1; //将void*指针转换成char*类型的指针(#需要注意一点:因为都是byte数据、所以不会报转换失败异常)
unsafe { int* intBlock = stackalloc int[100]; char* charBlock = stackalloc char[100]; }
using System.Runtime.InteropServices; //int length = 1024; //定义需要申请的内存块大小(1KB) int length = 1024 * 1024 * 1024; //定义需要申请的内存块大小(1GB) IntPtr address = Marshal.AllocHGlobal(length); //从非托管内存中申请内存空间,并返会该内存块的地址 (单位:字节) //相当于byte[length] //注意:申请内存空间不会立即在任务管理器中显示内存占用情况 try { #region Marshal - 写入 { Marshal.WriteByte(address, 111); //修改第一个byte中的数据 Marshal.WriteByte(address, 0, 111); //修改第一个byte中的数据 Marshal.WriteByte(address, 1, 222); //修改第二个byte中的数据 Marshal.WriteByte(address, length - 1, 255); //修改最后一个byte中的数据 (#此处需要注意,如果定义的偏移量超出则会误修改其他变量的数据) } #endregion #region Marshal - 读取 { int offset = length - 1; //定义读取最后一个byte的内容 byte buffer0 = Marshal.ReadByte(address); //读取第一个byte中的数据 byte buffer1 = Marshal.ReadByte(address, 0); //读取第一个byte中的数据 byte buffer2 = Marshal.ReadByte(address, 1); //读取第二个byte中的数据 byte buffer3 = Marshal.ReadByte(address, length - 1); //读取最后一个byte中的数据 } #endregion #region Marshal - 数组数据写入到目标内存块中 { //source可以是byte[]、也可以是int[]、char[]... byte[] source = new byte[] { 1, 2, 3 }; //将source变量的数组数据拷贝到address内存块中 Marshal.Copy(source: source, startIndex: 0, //从source的第一个item开始 length: 3, //选择source的3个item destination: address); //选择存储的目标 (会写到address内存块的开头处) } #endregion #region Marshal - 内存块数据读取到目标数组中 { //dest可以是byte[]、也可以是int[]、char[]... byte[] dest = new byte[5]; Marshal.Copy(source: address, destination: dest, //#注意:目标数组不能为空、且需要有足够的空间可接收数据 startIndex: 1, //从dest数组的第二个item开始 length: 3); //将address内存块的前3个item写入到dest数组中 } #endregion unsafe { int[] array = new int[5] { 1, 2, 3, 4, 5 }; int* p = (int*)Marshal.UnsafeAddrOfPinnedArrayElement(array, 1); //获取数组第二个item的内存地址、并转换成int类型的指针 char* p2 = (char*)Marshal.UnsafeAddrOfPinnedArrayElement(array, 1); //获取数组第二个item的内存地址、并转换成char类型的指针 } } finally { Marshal.FreeHGlobal(address); //释放非托管内存中分配出的内存 (释放后可立即腾出空间给系统复用) }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。