ref struct实例能否被GC移动?托管线程栈是否可被GC迁移?
关于ref struct与托管栈地址稳定性的问题
核心疑问
- ref struct仅能存活于栈上,那GC是否会移动托管线程的栈本身?
- 如下示例代码中,获取栈上ref struct的指针后,后续这个指针会不会失效?
Foo foo = new(); ref var fooRef = ref foo; Foo * ptr; fixed (Foo* p = &fooRef) ptr = p; // 此处`ptr`是否会失效? // 尽管我们知道Foo因被保证在栈上而不会移动?还是栈地址会变动? // 或许GC会移动整个线程进而移动其栈? ref struct Foo {}
结论与分析
GC不会移动托管线程的栈
托管线程的栈是由操作系统分配和管理的内存区域,GC只负责托管堆的内存管理(分配、回收、移动对象),完全不会干预栈内存的布局或移动。栈的地址在线程生命周期内是固定的,不会因为GC操作而改变。
示例代码中的ptr不会失效
foo是栈上的ref struct实例,只要它还处于当前作用域(未出栈),其内存地址就始终稳定。这里的fixed语句其实是冗余的——fixed的作用是固定堆上的可移动对象,防止GC移动它们,但ref struct本身就在栈上,不存在被GC移动的可能。离开fixed块后,ptr依然指向foo的有效栈地址,不会失效。
栈上树结构的实现安全性
背景:编写纯栈上的树结构,因C#不允许ref struct包含自身类型的ref字段,故使用指针实现:
using System; using System.Diagnostics; #pragma warning disable CS8500 readonly unsafe ref struct TreeNode<T> { [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly TreeNode<T> * parent; public readonly T Value; public TreeNode(T value) => Value = value; public TreeNode(T value, in TreeNode<T> parent) : this(value) { fixed (TreeNode<T> * ptr = &parent) this.parent = ptr; } public Boolean HasParent => parent != null; public ref TreeNode<T> Parent => ref *parent; }
这个实现是安全的,前提是所有TreeNode<T>实例都严格遵循ref struct的约束(存活于栈上):
- 栈上变量的地址在其生命周期内固定,父节点的指针不会因GC操作失效;
- 代码中的
fixed语句同样冗余,in参数传递的ref struct本身就在栈上,地址天然稳定,无需固定; - 需要注意父节点的生命周期必须长于子节点,否则子节点的
parent指针会指向已出栈的无效内存(栈内存出作用域后会被复用,但不会被GC回收)。
内容的提问来源于stack exchange,提问作者Nick Strupat
相关产品推荐
相关产品推荐

