平时写 C#,你基本不用操心内存在哪、对象会不会被挪走 ——垃圾回收器(GC)全给你安排得明明白白,还能自动整理内存,避免碎片。但有些特殊场景,比如:
1.写 unsafe 代码,用指针直接操作内存
2.调 Windows API、硬件驱动、老 DLL(P/Invoke)
3.做极致性能优化(比如游戏引擎、高频交易)
这时候,你就得告诉 GC:“大哥,这个对象别动!我正用指针指着它呢!”这就是 fixed 关键字的用武之地。
fixed 到底干了啥?
简单说:它把对象“钉”在内存里,不让 GC 移动它,直到 fixed 代码块结束。为什么需要这个?因为 GC 为了优化内存,会时不时把对象“搬家”(压缩堆)。如果你有个指针指向它,一搬家,指针就指向了“空气”——程序直接崩。fixed 就是给 GC 打个招呼:“这会儿别动它,我用完就放”。
示例 1:固定数组 —— 安全地用指针遍历
using System;
classProgram
{
unsafe static void Main()
{
int[] numbers = { 10, 20, 30, 40, 50 };
// 堆代码 duidaima.com
fixed (int* ptr = numbers) // 把数组钉住
{
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine(ptr[i]); // 指针安全有效
}
} // 出了这个花括号,GC 又可以自由移动数组了
}
}
✅ 没 fixed? GC 可能在你读 ptr[3] 时把数组挪走 → 程序崩溃
✅ 有 fixed? 指针全程有效,稳如老狗
示例 2:通过指针直接改数据 —— 原地操作,零拷贝
public unsafe static void ModifyArray()
{
int[] numbers = { 1, 2, 3, 4, 5 };
fixed (int* ptr = numbers)
{
for (int i = 0; i < numbers.Length; i++)
{
ptr[i] *= 10; // 直接改内存,不经过 C# 属性/方法
}
}
foreach (var n in numbers)
{
Console.WriteLine(n); // 输出:10, 20, 30, 40, 50
}
}
这种操作在高性能场景很常见:
1.图像处理(直接改像素)
2.音频解码(原地转换格式)
3.网络协议解析(直接读写 buffer)
⚠️ 注意事项 —— 别乱用,会出事!
可能导致内存碎片:GC 不能整理被 fixed 的内存,长期大量使用会让堆变得“坑坑洼洼”
只在必要时用:日常业务代码?别碰!能用 Span<T> 就用 Span<T>
作用域要短:fixed 块越小越好,别一钉就是几百行
✅ 推荐使用场景:
1.调非托管代码(P/Invoke)
2.unsafe 性能热点(经 Benchmark 证实需要优化)
3.与硬件/驱动交互
🚫 替代方案(更安全):
1.用 Span<T> / Memory<T> 处理内存切片(.NET Core 2.1+)
2.用 System.Buffers 管理缓冲池
3.用 MemoryMarshal 做安全的指针转换
💡 老司机建议:
“99% 的场景,Span<T> 能搞定;1% 的场景,才轮到 fixed 出手。”
✅ 总结
fixed = 告诉 GC:“别动,这对象我正用着呢!”
它在 本地代码交互 和 unsafe 性能优化 场景中是救命稻草。但在日常 C# 开发中,你可能一辈子都用不上它 ——这恰恰说明 .NET 的高层抽象足够好,让你远离了内存管理的泥潭。只有当你真的需要“下场手搓指针”时,fixed 才会成为你最可靠的伙伴。