C# 指向具有托管字段的“ref struct”指针的安全问题。忽略警告 CS8500

问题描述 投票:0回答:1

考虑以下代码:

    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
正在传递一个
ref struct
(
RefStructWithPointer
),其中包含指向另一个带有托管字段 (
ref struct
) 的
RefStructWithManagedFields
的指针。指针对外部是隐藏的,因为它以
ref property
的形式返回。从 C# 11.0 开始,这可以通过“ref fields”来完成,但是,我必须使用 9.0

据我了解,直接获取指向托管引用的指针是不安全的,因为 GC 可能会移动它,因此指针可能无效。如果您想要一个指针,则必须通过作用域内的

fixed
关键字“修复”它。

获取指向任何

struct
的指针也可能不安全,因为该结构可能作为另一个类的字段存在于堆中,因此如果您想要一个指针,也必须“修复”它。但是,如果它是本地范围的 var(堆栈分配),编译器将允许您避免修复它。

也允许使用指向

ref struct
的指针,无需修复,因为它可以确保位于堆栈中。

我不明白的是,当这些结构包含托管引用时,编译器会发出警告。我知道那些托管引用本身可以在堆上移动。但是,如果我们不直接使用指向它们的指针,而是指向包含它们的结构体。为什么很危险?

如果一个类通过 GC 移动,则必须更新对它的所有引用。因此,我假设

ref struct
中包含的托管引用也将得到更新。在任何安全上下文中,
ref struct
中的引用始终是安全的。如果“指向的”结构存在于堆栈中,为什么可能无法通过指针(指向结构)访问它?结构体中包含的引用不会更新吗?

在提供的示例中,调用

GetSomethingManaged()
SetSomethingManaged()
是否可能存在危险?为什么?

我无法复制这种危险的场景。我不理解通过指针访问具有托管字段的引用结构背后的危险,我的理解让我觉得它是安全的,而实际上危险的是其他东西。

c# pointers unsafe managed ref-struct
1个回答
0
投票

回复我自己。该警告的存在是由于“范围界定”。通过声明一个指针,即使它指向“引用结构”,如果像这样使用,您实际上可以允许它转义:

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
    }
}

所以,我的教训是:警告就是警告,只要小心一点,真正尝试打破它,看看你是否在做坏事

© www.soinside.com 2019 - 2024. All rights reserved.