正在获取指向自身内部结构的指针(不安全上下文)



长话短说,我将提供一个可能有用的简单示例:

public struct Vector3f {
    public float x;
    public float y;
    public float z;
    public unsafe float this[int index] {
        get {
            // Get "p" somehow, so that it points to "this"...
            return p[index];
        }
        set {
            // Get "p" somehow, so that it points to "this"...
            p[index] = value;
        }
    }
}

我想你明白我的意思了:

var v = new Vector3f();
Assert(v.x == v[0]);

编辑1:

对于那些仍然在问的人:)

Assert(v.y == v[1]);
Assert(v.z == v[2]);

编辑2:

fixed是否在此处产生冗余开销?或者这个结构已经被固定了,因此fixed在这里没有任何作用,只需要满足编译器的要求?可能的答案。

首先,我不会为此使用不安全的代码,除非我首先确定(1)带有开关的明显代码将是整个程序中最慢的代码,并导致显著的、用户可观察到的速度减慢,并且(2)使用不安全代码可以解决性能问题。

其次,如果我使用不安全的代码,那么对结构包装进行假设是非常危险的。CLR在选择如何打包结构方面有很大的灵活性如果你要做这件危险的事情,那么你应该使用structlayout属性来确保浮动正好在你需要的地方。

第三,是什么阻止有问题的调用者传递负索引或过大的索引?

第四:

fixed是否会在这里产生多余的开销?

我不知道"多余的开销"是什么意思。"fixed"使抖动告诉垃圾收集器"不要移动这个东西,因为我需要对它进行指针运算"。你正在进行短期修复,这是理想的;长时间修复会使集合更有可能因为固定的存储无法移动而变得一团糟。

第五:

或者这个结构已经被固定了,因此固定在这里没有任何作用,只需要满足编译器的要求?

也许吧!也许"this"所指的变量已经是一个固定变量了也许不是编译器应该如何知道结构的"this"是否是对固定存储的引用?我们必须假设最坏的情况,所以需要您修复它。

你的意思是这样的吗

get
{
    // (index validation omitted)
    fixed (Vector3f* thisPtr = &this)
    {
        return ((float*)thisPtr)[index];
    }
}

我承认它只解决了这个特定的情况,而不是"一般"情况,但你可以通过这样做来避免unsafe(特别是当你说你正在展示一个简化的例子时,但它可能对其他访问这个问题的人有用):

public struct Vector3f
{
    public float x;
    public float y;
    public float z;
    public float this[int index]
    {
        get
        {
            switch (index)
            {
                case 0:
                    return x;
                case 1:
                    return y;
                case 2:
                    return z;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
        set
        {
            switch (index)
            {
                case 0:
                    x = value;
                    break;
                case 1:
                    y = value;
                    break;
                case 2:
                    z = value;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
}

我同意Eric的观点,你可能不想这样做,我怀疑Rob的解决方案也做得很好(同时避免使用fixed)。不过,值得注意的是,如果使用LayoutKind.Explicit:,则可以重叠结构字段

[StructLayout(LayoutKind.Explicit)]
public struct Vector3f
{
    [FieldOffset(0)]
    private float x;
    [FieldOffset(sizeof(float))]
    private float y;
    [FieldOffset(2 * sizeof(float))]
    private float z;
    [FieldOffset(0)]
    private unsafe fixed float indexed[3];
    public Vector3f(float x, float y, float z)
    {
        this.x = x;
        this.y = y;
        this.z = z;
    }
    public float X { get { return x; } set { x = value; } }
    public float Y { get { return y; } set { y = value; } } 
    public float Z { get { return z; } set { z = value; } }
    public unsafe float this[int index]
    {
        get
        {
            if (index < 0 || index >= 3)
                throw new IndexOutOfRangeException();
            fixed (float* b = indexed)
                return b[index];
        }
        set
        {
            if (index < 0 || index >= 3)
                throw new IndexOutOfRangeException();
            fixed (float* b = indexed)
                b[index] = value;
        }
    }
}

最新更新