<p>必须在项目文件中启用<AllowUnsafeBlocks>true</AllowUnsafeBlocks>并用unsafe块包裹指针代码,否则即使写了int* p = &x;也会编译报错。</p>

为什么 unsafe 代码块里读内存还报错“无法在安全上下文中使用指针”
因为 C# 默认禁止指针操作,哪怕你写了 int* p = &x;,编译器也会直接拒绝。必须显式开启不安全上下文——不只是加 unsafe 关键字,还要让项目允许不安全代码。
实操建议:
- 项目文件(
.csproj)中添加<AllowUnsafeBlocks>true</AllowUnsafeBlocks> - 代码中用
unsafe显式包裹指针逻辑,不能只在方法签名写unsafe void Read()就完事 - 如果用的是 .NET SDK 风格项目,VS 里右键项目 → “属性” → “生成” → 勾选“允许不安全代码”,本质也是改这个配置项
Marshal.PtrToStructure 和直接指针解引用哪个更合适
取决于你手里的是一块原始内存地址(比如来自 Marshal.AllocHGlobal 或非托管 DLL 返回的 IntPtr),还是已知布局的托管对象首地址。前者必须用 Marshal.PtrToStructure;后者才能用指针直接访问。
常见错误现象:把 IntPtr 强转成 int* 后解引用,结果读出乱码或崩溃——大概率是没考虑结构体对齐、大小端、或目标内存根本没按预期布局。
实操建议:
- 从非托管侧拿到
IntPtr,优先走Marshal.PtrToStructure<T>(ptr),它会按[StructLayout]自动处理偏移和封送 - 真要用指针(比如高性能循环读取数组),先用
Marshal.UnsafeAddrOfPinnedArrayElement获取固定数组首地址,再 pin 住对象防止 GC 移动 - 别忘了用完
Marshal.FreeHGlobal释放手动分配的内存,否则就是稳定泄漏
读取过程中程序崩溃或数据错位,八成是没处理好内存生命周期
C# 的 GC 不知道你在用指针访问哪块内存,一旦对象被回收或移动,指针立刻变悬垂指针(dangling pointer)。这不是“偶尔出错”,而是必然问题,只是表现时机不确定。
使用场景举例:把托管数组传给非托管函数处理,同时在 C# 侧用指针读它的内容——如果没 pin 住,GC 可能在中途压缩堆,数组地址就变了。
实操建议:
- 用
GCHandle.Alloc(array, GCHandleType.Pinned)固定数组,再通过handle.AddrOfPinnedObject()拿地址 - 操作完立刻调用
handle.Free(),不 free 就等于长期 pin,影响 GC 效率 - 避免跨 await 边界持有指针——
async方法可能被挂起、恢复到不同线程,而 pinned handle 不跨线程生效
在 .NET 6+ 上用 Memory<byte> 和 Span<byte> 替代指针是否可行
绝大多数读内存的场景,完全可以不用指针。只要数据在托管内存里(比如 byte[]、string、ReadOnlyMemory<char>),Span 提供了零分配、无 unsafe 的安全访问方式,性能几乎一致。
性能影响:指针操作本身不慢,但绕过所有安全检查后,出错代价极高;而 Span 在 JIT 编译时做边界检查消除(bounds check elimination),Release 模式下基本无开销。
实操建议:
- 读字节数组?直接
Span<byte> span = data.AsSpan(); byte b = span[10]; - 需要转成结构体?用
MemoryMarshal.Read<MyStruct>(span),前提是结构体满足Unmanaged约束 - 只有当你必须对接 C ABI、处理未对齐内存、或写 SIMD 循环时,才值得回到
unsafe
真正容易被忽略的点:很多人以为“用了指针就更快”,其实多数时候只是把本来能用 Span 解决的问题,复杂化成了内存生命周期管理难题。









