我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C#指针内存控制Marshal内存数据存储原理分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C#指针内存控制Marshal内存数据存储原理分析

了解内存的原理

1、内存是由 Key 和 Value 组成,Key 是内存地址、Value 是存储的数据;

2、Key:是一个32位长度的二进制数;(64位的程序则是64位长度的二进制)

  • > 32位最大值为二进制 ‭0111 1111 1111 1111 1111 1111 1111 1111‬
  • 或十六进制 0x7FFF FFFF,或十进制 2 147 483 647 (2GB) (int.MaxValue);
  • > 在C#程序中由 IntPtr 类型进行存储,常以十六进制数进行交互;

3、Value:则是一个8位长度的二进制数;(所以说计算机只能存储 0 和 1 就是这原因)

  • > 最大值为二进制 1111 1111‬,或十六进制 0xFF,或十进制 255;
  • > 也就是 1byte 的数据,所以说计算机最小存储单位为 byte 也正是如此;

4、内存组成结构如下:

  • >  二进制:Key (0111 1111 1111 1111 1111 1111 1111 1111‬) = Value (1111 1111)
  • >  十六进制:Key (0x7FFF FFFF) = Value (0xFF)
  • >  十进制:Key (2 147 483 647) = Value (255)
  • >  程序:Key (IntPtr) = Value (byte)

了解指针的原理

1、指针是用于指向一个值类型数据,非想象中的面向过程逻辑、认为第一个读取后会自动指向下一个,哈哈;

2、如 int 类型的指针,就是将指定内存地址中的数据转换成 int 数据;

3、由于 int 类型长度为32位(4byte),所以指针读取数据时会自动取连续4byte的数据来转换成 int;

  • > 如一个 int 类型值为 123456,假设他的内存地址为 IntPtr(0x014245E0),那么他所占用的内存块则为以下:
  • 第1byte:IntPtr(0x014245E0) = byte(0x40)
  • 第2byte:IntPtr(0x014245E1) = byte(0xE2)
  • 第3byte:IntPtr(0x014245E2) = byte(0x01)
  • 第4byte:IntPtr(0x014245E3) = byte(0x00)
  • 组成结构为:IntPtr(0x014245E0) = byte[] { 0x40, 0xE2, 0x01, 0x00 }
  • > 那么下一个对象则就从 IntPtr(0x014245E4) 开始,如:IntPtr(0x014245E4) = byte[] { 0x00, 0x00, 0x00, 0x00 };

OK,说完原理得开始说代码了,来个华丽的分割线;

再声明一下: 

  • 1、由于 C# 程序中默认是不允许使用不安全代码,如内存控制、指针等操作;
  • 2、所以关于非安全操作的代码需要写在 unsafe 语句块中;
  • 3、另外还需要设置允许使用不安全代码,如:解决方案 > 选择项目 > 右键 > 属性 > 生成 > [√] 允许不安全代码;

1、通过指针修改 值类型 的变量数据

int val = 10;
 
unsafe
{
    int* p = &val;  //&val用于获取val变量的内存地址,*p为int类型指针、用于间接访问val变量
 
    *p *= *p;       //通过指针修改变量值(执行此操作后 val 变量值将会变成 100)
}

2、通过指针修改 引用类型 的变量数据

string val = "ABC";
 
unsafe
{
    fixed (char* p = val)   //fixed用于禁止垃圾回收器重定向可移动的变量,可理解为锁定引用类型对象
    {
        *p = 'D';           //通过指针修改变量值(执行此操作后 val 变量值将会变成 "DBC")
        p[2] = 'E';         //通过指针修改变量值(执行此操作后 val 变量值将会变成 "DBE")
        int* p2 = (int*)p;  //将char类型的指针转换成int类型的指针
    }
}

3、通过指针修改 数组对象 的成员数据

double[] array = { 0.1, 1.5, 2.3 };
 
unsafe
{
    fixed (double* p = &array[2])
    {
        *p = 0.2;           //通过指针修改变量值(执行此操作后 array 变量值将会变成{ 0.1, 1.5, 0.2 })
    }
}

4、通过指针修改 类对象 的字段数据

User val = new User() { age = 25 };
 
unsafe
{
    fixed (int* p = &val.age)   //fixed用于禁止垃圾回收器重定向可移动的变量,可理解为锁定引用类型对象
    {
        *p = *p + 1;            //通过指针修改变量值(执行此操作后 val.age 变量值将会变成 26)
    }
}
 

5、通过IntPtr自定义内存地址修改 值类型 数据

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

6、void* 一个任意类型的指针

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数据、所以不会报转换失败异常)

7、stackalloc 申请内存空间

unsafe
{
    int* intBlock = stackalloc int[100];
    char* charBlock = stackalloc char[100];
}

8、Marshal 操作内存数据

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);   //释放非托管内存中分配出的内存 (释放后可立即腾出空间给系统复用)
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C#指针内存控制Marshal内存数据存储原理分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C#指针内存控制Marshal内存数据存储原理分析

这篇文章主要介绍了C#指针内存控制Marshal内存数据存储原理分析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-02-26

C#指针内存控制Marshal内存数据存储原理是什么

本篇内容介绍了“C#指针内存控制Marshal内存数据存储原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!了解内存的原理1、内存是由
2023-07-05

C/C++中指针与内存管理的示例分析

这篇文章主要介绍了C/C++中指针与内存管理的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。指针和内存管理始终是C/C++比较容易模糊的知识点,但在C/C++编程中又
2023-06-29

C++浅析数据在内存中如何存储

使用编程语言进行编程时,需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着,当您创建一个变量时,就会在内存中保留一些空间。您可能需要存储各种数据类型的信息,操作系统会根据变量的数据类型,来分配内存和决定在保留内存中存储什么
2022-11-13

C++深入分析数据在内存中的存储形态

使用编程语言进行编程时,需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着,当您创建一个变量时,就会在内存中保留一些空间。您可能需要存储各种数据类型的信息,操作系统会根据变量的数据类型,来分配内存和决定在保留内存中存储什么
2023-01-06

C++ 函数指针参数的内存分配方式

c++++ 中函数指针参数可以采用动态分配或静态分配两种内存分配方式。动态分配使用堆内存,在运行时分配和释放内存;静态分配使用栈内存,在编译时分配内存。C++ 函数指针参数的内存分配方式函数指针是 C++ 中一种强大的工具,它允许我们将函
C++ 函数指针参数的内存分配方式
2024-04-20

C语言数据在内存中的存储流程深入分析

使用编程语言进行编程时,需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着,当您创建一个变量时,就会在内存中保留一些空间。您可能需要存储各种数据类型的信息,操作系统会根据变量的数据类型,来分配内存和决定在保留内存中存储什么
2022-11-13

理解 C++ 函数指针的内存管理:避免指针陷阱

在 c++++ 中使用函数指针时,必须谨慎考虑内存管理以避免陷阱。这些陷阱包括悬浮指针(指向超出其范围的函数)和野指针(从未初始化或设置为 nullptr 的函数指针)。为了避免这些陷阱,请遵循以下最佳实践:始终初始化函数指针,谨慎管理内存
理解 C++ 函数指针的内存管理:避免指针陷阱
2024-04-29

C++ 函数内存分配和销毁异常处理指南

c++++ 函数的内存分配和销毁异常可以通过遵循这些原则来避免:使用 raii 原则:使用智能指针自动释放资源。处理 nothrow 新运算符:在内存分配可能失败时返回 nullptr。使用析构函数:在对象销毁时释放分配的内存。C++ 函数
C++ 函数内存分配和销毁异常处理指南
2024-04-22

深入理解 C++ 函数内存分配和销毁机制

函数内存管理涉及自动变量(栈分配,函数返回时释放)和动态分配(堆分配,使用 new,需要手动释放)。函数调用时内存栈展开,每个调用分配自己的内存,释放时栈撤回到调用点。避免内存泄漏的关键是确保动态分配内存始终得到释放,例如使用智能指针或 r
深入理解 C++ 函数内存分配和销毁机制
2024-04-22

C++虚函数表与类的内存分布深入分析理解

对C++了解的人都应该知道虚函数(VirtualFunction)是通过一张虚函数表(VirtualTable)来实现的。简称为V-Table。本文就将详细讲讲虚函数表的原理与使用,需要的可以参考一下
2022-11-13

Solr搜索中的数据索引与存储机制分析(Solr如何管理搜索数据的索引和存储?)

Solr通过解析文档、词干化、建立倒排索引和段合并来管理索引。存储机制包括索引存储、数据存储和字段值存储。索引和存储协同工作,实现快速文档查找和字段值访问。优化策略包括分片、复制、回滚和快照。Solr架构使用Schema.xml定义索引架构,允许用户定制索引和存储行为。这些机制共同确保了Solr的高速和准确搜索能力,使其适用于海量数据搜索场景。
Solr搜索中的数据索引与存储机制分析(Solr如何管理搜索数据的索引和存储?)
2024-04-02

c语言中数据存储与原码、反码、补码的示例分析

这篇文章主要介绍了c语言中数据存储与原码、反码、补码的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1.数据的类型介绍在学习数据储存之前,让我们先认识一下数据类型。以
2023-06-29

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录