fixed用于固定托管对象以防gc移动,确保指针在块内安全使用;离开块后指针失效,继续使用属未定义行为。它可固定数组、字符串、字段、span等托管堆内存,但不可固定值类型变量或临时表达式。

fixed 不是用来“固定指针”的,而是用来固定托管对象、防止 GC 移动它,从而让指针在块内能安全使用。 把 fixed 理解成“钉住内存”比“固定指针”准确得多——指针本身是栈上变量,天然不被 GC 动;真正需要防的,是它指向的那个数组、字符串或对象实例。
为什么离开 fixed 块后不能用指针
GC 不会追踪指针是否还活着,只看托管引用是否还在。一旦 fixed 块结束,钉选解除,对象可能下一秒就被移动或回收。此时再读写该指针地址:
- 可能 crash(访问已释放内存)
- 可能看似正常但改了别的对象(踩内存)
- 可能读到旧值或乱码(对象被移动后原地址残留垃圾)
这不是概率问题,是未定义行为(UB)。编译器甚至可能优化掉你“以为还在用”的指针操作,导致逻辑静默失效。
fixed 能固定什么、不能固定什么
合法目标必须提供稳定、可取址的托管堆内存起点:
- ✅
int[]、byte[]等托管数组:fixed (byte* p = buffer) - ✅
string(只读):fixed (char* p = str)→ 得到 UTF-16 首字符地址 - ✅ 类/结构体字段:
fixed (int* p = &obj.x)→ 整个obj实例被钉住 - ✅
Span<t></t>或ReadOnlySpan<t></t>(底层为托管堆数组):fixed (int* p = span) - ❌
&str[0]:索引返回的是值,不是变量,地址不可取 - ❌
&localInt:局部值类型在栈上,天然固定,fixed不允许且无意义 - ❌
list.ToArray():返回新数组但无强引用,GC 可能在fixed入口前就回收它
多个数组一起 fixed 的写法和陷阱
常见于内存拷贝等场景,语法支持逗号分隔:
fixed (byte* pSrc = source, pDst = target)
{
byte* s = pSrc + srcOffset;
byte* d = pDst + dstOffset;
for (int i = 0; i <p>注意点:</p>
- 两个数组必须都非 null,否则
fixed语句本身会抛NullReferenceException - 不能混用不同类型的指针声明,比如
fixed (int* p = arr, char* q = str)是非法的 - 所有被
fixed的对象,在整个块执行期间都被钉住——哪怕你只用其中一个指针,另一个也付出 GC 开销
不加 fixed 的 unsafe 指针操作等于裸奔
有人试过这样写:
unsafe
{
int* p = arrayPtr; // arrayPtr 来自某处
*p = 42; // 看似没问题?
}
问题在于:如果 arrayPtr 是从托管数组取来的地址(比如之前用 fixed 拿过又存下来),那它早已失效;如果它是通过其他方式(如 Marshal.AllocHGlobal)分配的非托管内存,则根本不需要 fixed。关键判断只有一条:只要指针指向的是托管堆上的对象,且生命周期超出当前作用域,就必须用 fixed 包裹使用点。
最容易被忽略的复杂点是异步和跨方法传递:把指针转成 IntPtr 传出去,再在别处转回指针?只要原始对象没被 fixed 住整个生命周期,就是悬空指针。而 fixed 的作用域边界非常硬——仅限大括号内,不因任何封装、委托或线程切换而延长。










