Закрепите массив нулевой длины с помощью ключевого слова fixed

У меня есть код unsafe C#, который нельзя изменить и который предоставляет такие методы:

static unsafe void Foo(
    byte* a, int aLength,
    byte* b, int bLength,
    byte* c, int cLength,
    byte* d, int dLength,
    byte* e, int eLength);

Я вызываю эти методы следующим образом:

static void Bar(
    byte[] a, int aOffset, int aLength,
    byte[] b, int bOffset, int bLength,
    byte[] c, int cOffset, int cLength,
    byte[] d, int dOffset, int dLength,
    byte[] e, int eOffset, int eLength)
{
    fixed (byte* a_ = &a[aOffset])
    fixed (byte* b_ = &b[bOffset])
    fixed (byte* c_ = &c[cOffset])
    fixed (byte* d_ = &d[dOffset])
    fixed (byte* e_ = &e[eOffset])
    {
        Foo(a_, aLength,
            b_, bLength,
            c_, cLength,
            d_, dLength,
            e_, eLength);
    }
}

(Проверка аргумента опущена для краткости.)

Это хорошо работает, если только один из массивов байтов не имеет нулевой длины. В этом случае я получаю исключение IndexOutOfRangeException.

Индекс находился вне границ массива.

Как я могу предотвратить исключение, желательно без написания большого количества шаблонного кода и без переключения с fixed на что-то другое?

Небезопасные методы не читают и не записывают аргументы, длина которых равна нулю.


person dtb    schedule 02.06.2012    source источник


Ответы (3)


Вы не можете получить доступ ни к одному элементу в пустом массиве, поэтому замените любой из пустых массивов фиктивным массивом, содержащим элемент, к которому вы можете получить доступ:

if (aLength == 0) a = new int[1];
if (bLength == 0) b = new int[1];
if (cLength == 0) c = new int[1];
if (dLength == 0) d = new int[1];
if (eLength == 0) e = new int[1];
person Guffa    schedule 02.06.2012

Поскольку он не читает и не записывает в массивы с нулевой длиной, вы можете изменить

fixed (byte* a_ = &a[aOffset])
fixed (byte* b_ = &b[bOffset])
fixed (byte* c_ = &c[cOffset])
fixed (byte* d_ = &d[dOffset])
fixed (byte* e_ = &e[eOffset])

to

fixed (byte* a_ = (a.Length == 0 ? (byte*)IntPtr.Zero : &a[aOffset]))
fixed (byte* b_ = (b.Length == 0 ? (byte*)IntPtr.Zero : &b[aOffset]))
fixed (byte* c_ = (c.Length == 0 ? (byte*)IntPtr.Zero : &c[aOffset]))
fixed (byte* d_ = (d.Length == 0 ? (byte*)IntPtr.Zero : &d[aOffset]))
fixed (byte* e_ = (e.Length == 0 ? (byte*)IntPtr.Zero : &e[aOffset]))

однако, если вы сделаете это, а затем попытаетесь прочитать или записать один из пустых массивов, вы получите исключение нулевого указателя.

person HypnoToad    schedule 02.06.2012
comment
Ошибка компилятора Правая часть присваивания фиксированного оператора не может быть выражением приведения - person dtb; 03.06.2012
comment
Да, похоже, С# не нравится ? внутри фиксированного инициализатора - person HypnoToad; 03.06.2012
comment
Тогда я бы согласился с предложением фиктивного массива Гуффы. - person HypnoToad; 03.06.2012

Is

static void Bar( ... )
{
  byte[] dummy = new byte[1];
  fixed (byte* a_ = a.Lenght>0 ? &a[aOffset] : &dummy[0])
  ...
}

слишком громоздкий?

Изменить:

Если оставить вышеизложенное только по историческим причинам, оператор ? не будет работать.

Поэтому вам нужно обойтись без ключевого слова fixed и сделать что-то вроде

public unsafe struct UnsafePointerStruct
{
    public GCHandle gch;
    public byte* addr;
}

static unsafe UnsafePointerStruct GetUnsafePointer(Array a, int ai)
{
    UnsafePointerStruct ups=new UnsafePointerStruct();
    ups.gch=GCHandle.Alloc(a,GCHandleType.Pinned);
    if (a.Length<=ai) ups.addr=(byte*)IntPtr.Zero;
    ups.addr=(byte*)Marshal.UnsafeAddrOfPinnedArrayElement(a,ai);
    return ups;
}

static unsafe void Bar(
    byte[] a, int aOffset, int aLength,
    byte[] b, int bOffset, int bLength,
    byte[] c, int cOffset, int cLength,
    byte[] d, int dOffset, int dLength,
    byte[] e, int eOffset, int eLength)
{
    UnsafePointerStruct upsa=GetUnsafePointer(a,aOffset);
    UnsafePointerStruct upsb=GetUnsafePointer(b,bOffset);
    UnsafePointerStruct upsc=GetUnsafePointer(c,cOffset);
    UnsafePointerStruct upsd=GetUnsafePointer(d,dOffset);
    UnsafePointerStruct upse=GetUnsafePointer(e,eOffset);

    Foo(upsa.addr, aLength,
        upsb.addr, bLength,
        upsc.addr, cLength,
        upsd.addr, dLength,
        upse.addr, eLength);

    upsa.gch.Free();
    upsb.gch.Free();
    upsc.gch.Free();
    upsd.gch.Free();
    upse.gch.Free();
}
person Eugen Rieck    schedule 02.06.2012
comment
Есть ли проблема с разделением манекена между вызовами методов, например. в многопоточных сценариях? - person dtb; 03.06.2012
comment
Я бы так не думал, но хорошей защитной практикой было бы переместить его за пределы метода и закрепить с помощью дескриптора GC в статическом конструкторе. - person Eugen Rieck; 03.06.2012
comment
Хм.. Я всегда думал, что объекты нужно закреплять как можно короче. - person dtb; 03.06.2012
comment
Большая продолжительность закрепления объектов значительно снижается, если рассматриваемые объекты являются статическими и выделяются рано (т. Е. Они находятся близко к началу кучи объектов) - person Eugen Rieck; 03.06.2012
comment
? не работает: похоже, это препятствие, дай мне минутку - person Eugen Rieck; 03.06.2012