获取指向struct内部的指针(不安全的上下文)

时间:2012-01-06 12:33:52

标签: c# pointers struct unsafe

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

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在这里没有效果,只需要满足编译器的需要? Possible answer

4 个答案:

答案 0 :(得分:8)

首先,我不会使用不安全的代码,除非我首先确定(1)带有开关的明显代码整个程序中最慢的代码并导致重要的用户 - 可观察的减速,以及(2)转向不安全的代码修复了性能问题。

其次,如果我使用不安全的代码,那么对结构打包做出假设是非常危险的。 CLR在如何选择打包结构方面具有广泛的优势。 如果你打算做这个危险的事情,那么你应该使用struct layout属性来确保浮动正好在你需要它的地方。

第三,是什么阻止了一个错误的调用者传递否定索引或过大的索引?

第四:

  

修复是否会在此处创建冗余开销?

我不知道“冗余开销”意味着什么。 “fixed”使得抖动告诉垃圾收集器“不要移动这个东西,因为我需要对它进行指针运算”。你正在修理一段时间,这是理想的;长时间固定会使收藏品更加混乱,因为收起的存储空间无法移动。

第五:

  

或许这个结构已经修复,因此修复在这里没有效果,只需要满足编译器的需要?

也许吧!也许“this”引用的变量已经是固定变量。 可能不是。 编译器如何知道结构的“this”是否是对固定存储的引用?我们必须假设最坏的情况,所以你需要修复它。

答案 1 :(得分:5)

你的意思是这样的吗?

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

答案 2 :(得分:1)

我承认它只解决了这个具体案例,而不是“一般”案例,但你可以通过做这样的事情来避免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();
            }
        }
    }
}

答案 3 :(得分:0)

我同意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;
        }
    }
}