Получение указателя на структуру внутри себя (небезопасный контекст) - PullRequest
5 голосов
/ 06 января 2012

Короче говоря, я приведу упрощенный пример, где это может быть полезно:

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 здесь не имеет никакого эффекта и нужна только для удовлетворения компилятора? Возможный ответ .

Ответы [ 4 ]

8 голосов
/ 06 января 2012

Во-первых, я бы не использовал небезопасный код для этого, если бы сначала не определил (1), что очевидный код с переключателем будет самый медленный код во всей программе и вызывает значительный пользовательский наблюдаемые замедления и то, что (2) переход к небезопасному коду устраняет проблему производительности.

Во-вторых, если бы я использовал небезопасный код, было бы чрезвычайно опасно делать предположения об упаковке структуры. CLR допускается широкая широта в способах упаковки конструкций. Если вы собираетесь делать эту опасную вещь, вам следует использовать атрибут struct layout, чтобы гарантировать, что числа с плавающей точкой находятся именно там, где вам нужно.

В-третьих, что мешает вызывающей стороне с ошибкой передать отрицательный индекс или слишком большой индекс?

Четвертое:

Является ли исправленное здесь избыточным?

Я не знаю, что означает "избыточные накладные расходы". "fixed" заставляет дрожание сообщать сборщику мусора "не перемещайте эту вещь, потому что мне нужно сделать арифметику с указателями на ней". Вы исправляете в течение короткого периода, который идеален; исправление в течение длительного времени повышает вероятность того, что коллекция будет испорчена, поскольку закрепленное хранилище не может быть перемещено.

Пятый:

Или, может быть, эта структура уже исправлена, и поэтому исправленная здесь не имеет никакого эффекта и необходима только для удовлетворения компилятора?

Может быть! Возможно, переменная, на которую указывает this, уже является фиксированной переменной. Возможно, это не так. Как компилятор должен знать, является ли "this" структуры ссылкой на фиксированное хранилище или нет? Мы должны предположить худшее, поэтому вы должны это исправить.

5 голосов
/ 06 января 2012

Вы имеете в виду что-то подобное?

get
{
    // (index validation omitted)
    fixed (Vector3f* thisPtr = &this)
    {
        return ((float*)thisPtr)[index];
    }
}
1 голос
/ 06 января 2012

Я допускаю, что это решает только этот конкретный случай, а не "общий" случай, но вы могли бы избежать 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();
            }
        }
    }
}
0 голосов
/ 07 января 2012

Я согласен с Эриком, что вы, вероятно, не хотите этого делать, и я подозреваю, что решение Роба делает то же самое (хотя и избегает использования 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;
        }
    }
}
...