必须启用AllowUnsafeBlocks且配合fixed语句:前者是编译硬性前提,后者防止GC移动托管对象导致悬空指针或崩溃;二者缺一不可。

不启用 AllowUnsafeBlocks,写再多 unsafe 块也编译不过;不加 fixed 就对托管数组取地址,运行时大概率读到错乱内存或直接崩溃。
必须在 .csproj 里配 AllowUnsafeBlocks
这是硬性前提,缺一不可。VS 里勾选“允许不安全代码”只是图形化操作,底层仍是改 .csproj 文件。
- SDK 风格项目(.NET 5+)只认这个配置:
<PropertyGroup> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup>
- 命令行构建必须显式传参:
dotnet build /p:AllowUnsafeBlocks=true - 老式项目可能还需额外设
<CompileWithUnsafe>true</CompileWithUnsafe>,但优先检查AllowUnsafeBlocks是否生效
fixed 不是可选项,是保命语句
托管堆上的数组、字符串、对象字段,地址随时会被 GC 移动。fixed 的唯一作用就是告诉 GC:“这块内存别动”,它生成的指针才真正可用。
- 错误写法:
byte[] data = new byte[1024]; byte* ptr = data;—— 编译直接失败 - 正确写法:
byte[] data = new byte[1024]; fixed (byte* ptr = data) { *ptr = 1; // 此时 ptr 才有效 } - 切勿把
ptr存到类字段、传给异步任务或返回出去——fixed块一结束,指针立刻悬空 - 对
string使用fixed时注意:默认是 UTF-16,char*指向的是char序列,不是byte
慎用 stackalloc,尤其别碰 async 和大尺寸
stackalloc 在栈上分配内存,快但极危险:超出线程栈默认大小(通常 1MB)就静默栈溢出,不抛异常,进程直接终止。
- 安全阈值建议 ≤ 8 KB,尤其在递归或深度调用链中要更保守
-
stackalloc int[1000000]看似只占 4 MB,但实际可能触发栈探针失败,上线后随机崩 - 绝对不能在
async方法里用stackalloc——栈帧会随await消失,指针立即失效 - 替代方案:
Marshal.AllocHGlobal分配非托管堆内存,用完必须Marshal.FreeHGlobal
优先用 System.Runtime.CompilerServices.Unsafe
这个静态类提供类型安全的内存操作原语,无需 unsafe 上下文,且 JIT 能优化成单条 CPU 指令,性能几乎等同指针,却规避了大部分风险。
- 比如
Unsafe.Add<T>(ptr, offset)替代ptr + offset -
Unsafe.Read<int>(ptr)替代*(int*)ptr - 它不涉及指针逃逸、GC 移动、栈生命周期等问题,适合多数高性能场景
- 只有当你需要跨类型 reinterpret(如把
int*当float*读)或精细控制内存对齐时,才真得碰裸指针
真正容易被忽略的不是语法,而是生命周期边界:所有指针的有效性都绑定在某个确定的作用域内,一旦脱离——哪怕只是多一次 await、多一次方法返回、多一次字段赋值——就不再是“指针”,而是“悬空地址”。


















