C# 不安全代码(unsafe)详解:原理、语法、示例与实战应用
关键词 C#、unsafe、不安全代码、指针、fixed、stackalloc、内存优化、CSDN教程前言很多刚学习 C# 的同学都会有一个疑问C# 不是一门安全的托管语言吗为什么还有“不安全代码unsafe”事实上虽然 C# 默认运行在CLR公共语言运行时上拥有垃圾回收GC、类型安全、内存保护等优势但微软仍然提供了unsafe不安全代码机制使开发者能够像 C/C 一样直接操作内存。这意味着可以使用指针可以访问内存地址可以提高某些场景的性能可以与 C/C DLL 更方便地交互当然能力越大责任越大。错误使用 unsafe很容易造成内存越界程序崩溃数据损坏安全漏洞因此unsafe 更多用于底层开发而不是普通业务开发。一、什么是不安全代码Unsafe Code官方定义Unsafe Code 是允许使用指针直接操作内存的 C# 代码。例如unsafe { int number 100; int* p number; Console.WriteLine(*p); }这里已经和 C/C 十分相似int a 100; int* p a; printf(%d, *p);所以unsafe C# 的指针模式。二、为什么需要 unsafe大多数情况下我们完全不需要 unsafe。因为 C# 已经提供ListSpanMemoryArrayLINQGC 自动管理但是下面这些场景仍然需要1、调用 C/C DLL例如Windows APIOpenCVFFmpegDirectXUnity Native Plugin这些库大量使用指针。2、高性能开发例如游戏引擎图像处理视频处理AI 推理数据库网络框架直接操作内存比数组更快。3、操作硬件例如串口PLCUSB工业控制驱动开发4、自己实现数据结构例如内存池对象池链表哈希表缓存系统很多都会使用 unsafe。三、如何开启 unsafeVisual Studio项目↓属性↓生成Build↓勾选Allow unsafe code或者修改 csprojPropertyGroup AllowUnsafeBlockstrue/AllowUnsafeBlocks /PropertyGroup四、unsafe 关键字可以修饰类unsafe class Test { }方法unsafe void Print() { }代码块unsafe { }最常见的是代码块unsafe { // 指针代码 }五、指针Pointer声明int* p;星号 *表示指向 int 类型的地址。例如unsafe { int age 20; int* p age; Console.WriteLine(*p); }输出20这里age ↓ 20而p ↓ 0x0012FF40即p 保存的是地址六、取地址符例如int number 100; int* p number;这里表示获取变量地址。类似 Ca七、解引用*例如Console.WriteLine(*p);这里*表示根据地址获取值。例如p ↓ 0x12345678 ↓ 100也可以修改值unsafe { int number 100; int* p number; *p 500; Console.WriteLine(number); }输出500说明修改的是内存。八、多个指针unsafe { int a 10; int b 20; int* p1 a; int* p2 b; Console.WriteLine(*p1); Console.WriteLine(*p2); }输出10 20九、指针运算和 C 一样p;例如unsafe { int[] arr {1,2,3,4}; fixed(int* p arr) { Console.WriteLine(*(p 2)); } }输出3因为p ↓ 1 ↓ 2 ↓ 3十、fixed 关键字这是 unsafe 中最重要的知识。为什么需要 fixed因为GC 会移动对象。例如GC ↓ 压缩内存 ↓ 对象地址改变如果指针还保存旧地址程序就会崩溃。因此fixed(...) { }就是固定对象地址。例如unsafe { int[] nums {1,2,3}; fixed(int* p nums) { Console.WriteLine(*p); } }输出1十一、遍历数组unsafe { int[] nums {10,20,30,40}; fixed(int* p nums) { for(int i0;i4;i) { Console.WriteLine(*(pi)); } } }输出10 20 30 40十二、stackalloc作用在栈上分配内存。例如unsafe { int* numbers stackalloc int[5]; for (int i 0; i 5; i) { numbers[i] i * 10; } for (int i 0; i 5; i) { Console.WriteLine(numbers[i]); } }输出0 10 20 30 40特点不经过 GC自动释放性能高生命周期仅限当前方法十三、sizeofunsafe 中可以使用Console.WriteLine(sizeof(int));输出4其他类型类型大小字节byte1short2int4long8float4double8char2bool1实现相关通常如此十四、unsafe 与 SpanT.NET Core 之后更推荐使用SpanT MemoryT ReadOnlySpanT例如Spanint span stackalloc int[5]; for (int i 0; i span.Length; i) { span[i] i; } foreach (var item in span) { Console.WriteLine(item); }相比裸指针更安全无需 fixed支持边界检查性能接近 unsafe更符合现代 .NET 编程方式在现代 .NETCore/.NET 5/.NET 6开发中除非必须与原生代码交互或实现极致性能优化否则优先选择SpanT而不是裸指针。十五、unsafe 的优缺点优点✅ 可以直接访问内存性能更高。✅ 能方便调用 C/C 原生库。✅ 支持底层开发图像处理、游戏引擎、驱动、网络框架等。✅ 可减少部分场景下的内存复制。缺点❌ 容易出现内存越界和非法访问。❌ 绕过 CLR 的部分安全保护机制。❌ 调试难度较高。❌ 可读性和维护成本较高不适合普通业务代码。十六、常见面试题1、什么是 unsafe允许 C# 使用指针直接操作内存的代码模式需要通过unsafe关键字声明并在项目中启用AllowUnsafeBlocks。2、为什么要使用 fixed因为垃圾回收器GC可能在内存整理时移动托管对象fixed可以在代码块执行期间固定对象地址避免指针失效。3、什么时候应该使用 unsafe调用 C/C 原生库P/Invoke图像、音视频处理游戏引擎或高性能计算自定义内存管理、对象池等底层组件普通 CRUD 或企业级业务开发通常无需使用。4、stackalloc 与 new 有什么区别对比项stackallocnew分配位置栈托管堆是否受 GC 管理否是生命周期当前方法结束自动释放由 GC 回收分配速度快相对较慢适用场景小型临时缓冲区普通对象和数组十七、总结unsafe是 C# 提供的一项高级特性它打破了托管环境的部分限制让开发者能够像 C/C 一样直接操作内存。它在高性能计算、图像处理、游戏开发以及与原生代码交互等场景中具有重要价值但同时也带来了更高的开发风险和维护成本。对于大多数业务开发者而言应优先使用SpanT、MemoryT、数组以及其他现代 .NET API来满足性能需求只有在经过性能分析确认确有必要或必须进行底层内存操作时再考虑使用unsafe。

相关新闻