C# Unsafe指针与Marshal内存操作详解

林舟
林舟
发布时间:2026-10-13| 2人浏览过
原创

本文深入解析C#中的不安全代码(unsafe)、指针原理及Marshal内存操作方法。通过实例演示如何通过指针修改值类型、引用类型及数组数据,掌握stackalloc栈内存分配,并利用System.Runtime.InteropServices.Marshal类进行非托管内存的申请、读写、释放及结构体编组,实现高效的底层内存控制。

内存基础架构

计算机内存由地址(Key)和数据(Value)组成。32位系统内存寻址为32位二进制数(最大2GB),64位系统则为64位。数据以8位字节(Byte,最小存储单位)为单位存储。在程序开发中,内存地址通常使用 IntPtr 类型表示,并以十六进制呈现。

指针的核心机制

指针用于直接访问指定内存地址的数据。读取时,会根据指针指向的数据类型长度截取相应大小的连续字节。例如,int 类型的指针会自动读取连续的4个字节,并将其转换为整型数值。

编译与安全环境配置

C# 默认启用安全检查,若需进行内存控制或指针操作,必须执行以下两步:

  1. 在代码中使用 unsafe 关键字包裹相关语句块。
  2. 在项目属性中启用不安全代码:进入项目 → 右键“属性” → “生成”选项卡 → 勾选“允许不安全代码”(Allow unsafe code)。

基础内存操作实战

第1步:修改值类型变量

目的:获取变量地址并通过指针修改其原始值。
核心代码:

int val = 10;
unsafe
{
    int* p = &val;      // 获取 val 的内存地址
    *p *= *p;           // 间接赋值,val 变为 100
}

第2步:锁定并修改引用类型

目的:防止垃圾回收器(GC)移动字符串等可变引用对象,通过 fixed 关键字将堆内存锁定并转为指针。
核心代码:

string val = "ABC";
unsafe
{
    fixed (char* p = val)  // 锁定字符串内存区域
    {
        *p = 'D';          // 修改首个字符,val 变为 "DBC"
        p[2] = 'E';        // 修改第三个字符,val 变为 "DBE"
    }
}

第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)
    {
        *p += 1;      // val.age 变为 26
    }
}
public class User { public string name; public int age; }

第5步:基于IntPtr自定义地址操作

目的:模拟极端的内存读写场景(如驱动交互、逆向分析)。
注意:写入不可读写的物理或受保护地址会引发安全异常。

unsafe
{
    char val = 'A';
    IntPtr address = (IntPtr)&val;
    byte* p = (byte*)address;
    *p = (byte)'B';   // 改变 val 的值
}

第6步:void* 通用指针的使用

说明:void* 是一种不绑定具体数据类型的任意指针,常用于接收外部传入的通用内存句柄。使用时必须进行显式强转(如转为 int*)方可按类型存取数据。

第7步:使用 stackalloc 分配栈空间

优势:stackalloc 直接在调用栈上分配未初始化内存,速度远快于堆分配,无需手动 GC 释放。适用于高频短生命周期的缓冲区。

unsafe
{
    int* buffer = stackalloc int[100]; // 分配400字节的栈内存
}

Marshal 与非托管内存控制

涉及跨进程通信、大文件处理及原生 C/C++ 库交互时,必须依赖 System.Runtime.InteropServices.Marshal 类。

内存的申请与释放

使用 AllocHGlobal 从非托管堆申请大块内存,操作完成后必须使用 FreeHGlobal 释放,否则会造成严重的内存泄漏。


using System.Runtime.InteropServices;

try 
{
    // 申请 1GB 空间
    IntPtr ptr = Marshal.AllocHGlobal(1024 * 1024 * 1024);
    
    // 写入单个字节
    Marshal.WriteByte(ptr, offset: 0, value: 111);
    
    // 批量拷贝:将托管数组拷贝至非托管内存
    byte[] source = new byte[] { 1, 2, 3 };
    Marshal.Copy(source, startIndex: 0, destination: ptr, length: 3);
    
    // 反向拷贝:读取至托管数组
    byte[] dest = new byte[5];
    Marshal.Copy(source: ptr, destination: dest, startIndex: 1, length: 3);
} 
finally 
{
    Marshal.FreeHGlobal(ptr); 
}

数组元素的精准寻址

当托管数组被 GCHandle 固定或处于非移动状态时,可使用 UnsafeAddrOfPinnedArrayElement 获取数组内特定索引的绝对地址,方便进行高性能的指针遍历。

以上就是C#中Unsafe指针与Marshal内存操作的详细内容,更多关于C#底层性能优化的资料请关注本站其它相关文章!
相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。