提问人:Canijo 提问时间:11/18/2023 最后编辑:Canijo 更新时间:11/20/2023 访问量:36
指向具有托管字段的“ref struct”指针的 C# 安全问题。忽略警告 CS8500
C# safety concerns for pointer to "ref struct" with managed fields. Ignoring warning CS8500
问:
请考虑以下代码:
public class SomeClass
{
public bool someBool;
}
public ref struct RefStructWithManagedFields
{
private SomeClass? _managedField;
internal void SetSomethingManaged(SomeClass something)
{
_managedField = something;
}
internal SomeClass? GetSomethingManaged()
{
return _managedField;
}
}
public unsafe ref struct RefStructWithPointer
{
/// emits warning CS8500:
/// "This takes the address of, gets the size of, or declares a pointer to a managed type ('RefStructWithManagedFields')
private readonly RefStructWithManagedFields* _pointer;
public ref RefStructWithManagedFields RefField { get => ref *_pointer; }
internal RefStructWithPointer(RefStructWithManagedFields* pointer)
{
_pointer = pointer;
}
}
class SomeContext
{
private SomeClass someClass = new();
public void DoSomething(ref RefStructWithPointer structWithPointer)
{
// set a managed field on a struct internally referenced with a pointer
structWithPointer.RefField.SetSomethingManaged(someClass);
}
}
class AnotherContext
{
public void DoSomething(ref RefStructWithPointer structWithPointer)
{
// get a managed field from a struct internally referenced with a pointer
SomeClass? someOtherClass = structWithPointer.RefField.GetSomethingManaged();
Console.WriteLine(someOtherClass?.someBool);
}
}
public class Program
{
static SomeContext someContext = new();
static AnotherContext anotherContext = new();
unsafe static void Main()
{
RefStructWithManagedFields foo = default;
/// emits warning CS8500:
/// "This takes the address of, gets the size of, or declares a pointer to a managed type ('RefStructWithManagedFields')
RefStructWithPointer bar = new(&foo);
someContext.DoSomething(ref bar);
/// garbage for the sake of argument
GeneratedALotOfGarbage();
GC.Collect();
anotherContext.DoSomething(ref bar);
}
}
Program
传递一个 (),该 () 包含指向另一个带有托管字段 () 的指针。指针在外部是隐藏的,因为它是作为 .这可以通过从 C# 11.0 开始的“ref 字段”来完成,但是,我绑定到 9.0ref struct
RefStructWithPointer
ref struct
RefStructWithManagedFields
ref property
据我了解,直接获取指向托管引用的指针是不安全的,因为 GC 可能会移动它,因此指针可能无效。如果需要指针,则必须通过作用域内的关键字来“修复”它。fixed
获取指向任何指针也可能是不安全的,因为该结构可能作为另一个类的字段存在于堆中,因此,如果您想要指针,也必须“修复”它。但是,如果它是本地范围的 var(分配的堆栈),编译器将允许您避免修复它。struct
在没有修复的情况下,也允许使用指向 a 的指针,因为它可以确保存在于堆栈上。ref struct
我不明白的是,当这些结构包含托管引用时,编译器会发出警告。我知道这些托管引用本身可以移动,因为它们在堆上。但是,如果我们不是直接指向它们,而是指向包含它们的结构。为什么它很危险?
如果一个类通过 GC 移动,则必须更新对它的所有引用。因此,我假设包含在 中的托管引用也将得到更新。在任何安全的上下文中,其中的引用始终是安全的。如果“指向”结构存在于堆栈上,为什么不能通过指针(指向结构体)访问它?结构中包含的引用不会更新吗?ref struct
ref struct
在提供的示例中,是否有任何方式可以调用或可能很危险?为什么?GetSomethingManaged()
SetSomethingManaged()
我无法复制这种情况很危险。我不明白通过指针访问带有托管字段的 ref-struct 背后的危险,我理解的让我觉得它是安全的,而真正危险的是别的东西。
答:
回复自己。由于“范围”而存在警告。通过声明一个指针,即使它是指向“ref struct”的,你实际上也可以允许它转义,如果像这样使用:
public class Program
{
static SomeContext someContext = new();
static AnotherContext anotherContext = new();
unsafe static void Main()
{
DoPointerThings(out var crash);
crash.RefField.GetSomethingManaged(); /// boom, we are pointing to a ref struct that is not longer allocated
}
private unsafe static void DoPointerThings(out RefStructWithPointer output)
{
RefStructWithManagedFields foo = default;
/// emits warning CS8500:
/// "This takes the address of, gets the size of, or declares a pointer to a managed type ('RefStructWithManagedFields')
output = new(&foo);
someContext.DoSomething(ref output); /// this is ok
anotherContext.DoSomething(ref output); /// also ok
}
}
所以,我的教训是:警告就是警告,只要小心,真正尝试打破它,看看你是否在做坏事
编辑:天哪......这实际上也可以用普通的非托管结构来完成,即使没有警告......所以我取消了将其标记为答案,因为它只是指出了指针的一般危险,而不是专门解决该问题,警告指的是具有托管字段的结构......不好意思。
评论
ref struct
ref struct
ref