Bu güvenli olmayan kod .NET Core 3'te de çalışmalı mıdır?


42

Span<T>Mümkünse yığın ayırmalarını önlemek için kullanmak üzere kütüphanelerimi yeniden düzenliyorum, ancak daha eski çerçeveleri de hedeflediğim için bazı genel yedek çözümler de uyguluyorum. Ama şimdi garip bir sorun buldum ve .NET Core 3'te bir hata bulduğumdan mı yoksa yasadışı bir şey mi yaptığımdan emin değilim.

Sorun:

// This returns 1 as expected but cannot be used in older frameworks:
private static uint ReinterpretNew()
{
    Span<byte> bytes = stackalloc byte[4];
    bytes[0] = 1; // FillBytes(bytes);

    // returning bytes as uint:
    return Unsafe.As<byte, uint>(ref bytes.GetPinnableReference());
}

// This returns garbage in .NET Core 3.0 with release build:
private static unsafe uint ReinterpretOld()
{
    byte* bytes = stackalloc byte[4];
    bytes[0] = 1; // FillBytes(bytes);

    // returning bytes as uint:
    return *(uint*)bytes;
}

İlginçtir, ReinterpretOld.NET Framework ve .NET Core 2.0'da iyi çalışır (sonuçta bundan memnun olabilirim), yine de beni biraz rahatsız ediyor.

Btw. ReinterpretOld.NET Core 3.0'da küçük bir değişiklikle de düzeltilebilir:

//return *(uint*)bytes;
uint* asUint = (uint*)bytes;
return *asUint;

Benim sorum:

Bu bir hata mıdır yoksa ReinterpretOldeski çerçevelerde yalnızca yanlışlıkla mı çalışır ve düzeltmeyi onlar için de uygulamalıyım?

Uyarılar:

  • Hata ayıklama derlemesi .NET Core 3.0'da da çalışır
  • Ben uygulamak için çalıştı [MethodImpl(MethodImplOptions.NoInlining)]etmek ReinterpretOldama hiçbir etkisi olmadı.

2
FYI: return Unsafe.As<byte, uint>(ref bytes[0]);veya return MemoryMarshal.Cast<byte, uint>(bytes)[0];- kullanmaya gerek yok GetPinnableReference(); diğer
uca

Başkalarına yardım etmesi durumunda SharpLab . Kaçınılması Span<T>gereken iki versiyon farklı IL'ye derlenir. Ben yok bence sen şey geçersiz Güncel yapıyoruz: Bir JIT hata şüpheli.
canton7

gördüğün çöp nedir? hack'i locals-init'i devre dışı bırakmak için mi kullanıyorsunuz? bu kesmek önemli ölçüde etkiler stackalloc(yani ayrılan alanı silmez)
Marc Gravell

@ canton7 aynı IL derlemek, biz bir JIT hata çıkarım olamaz ... IL aynı ise, vb ... daha büyük bir derleyici hata gibi geliyor, bir şey, belki de eski bir derleyici ile? György: Bunu nasıl derlediğini söyleyebilir misin? örneğin, hangi SDK? Çöpü kopyalayamıyorum
Marc Gravell

1
Görünüşe göre stackalloc her zaman sıfır değildir, aslında: link
canton7

Yanıtlar:


35

Ooh, bu eğlenceli bir keşif; burada olan şey, yerelinizin optimize edilmesidir - kalan yerel halk yoktur .locals init, yani hayır yoktur , bu farklıstackalloc davranır ve alanı silmez;

private static unsafe uint Reinterpret1()
{
    byte* bytes = stackalloc byte[4];
    bytes[0] = 1;

    return *(uint*)bytes;
}

private static unsafe uint Reinterpret2()
{
    byte* bytes = stackalloc byte[4];
    bytes[0] = 1;

    uint* asUint = (uint*)bytes;
    return *asUint;
}

dönüşür:

.method private hidebysig static uint32 Reinterpret1() cil managed
{
    .maxstack 8
    L_0000: ldc.i4.4 
    L_0001: conv.u 
    L_0002: localloc 
    L_0004: dup 
    L_0005: ldc.i4.1 
    L_0006: stind.i1 
    L_0007: ldind.u4 
    L_0008: ret 
}

.method private hidebysig static uint32 Reinterpret2() cil managed
{
    .maxstack 3
    .locals init (
        [0] uint32* numPtr)
    L_0000: ldc.i4.4 
    L_0001: conv.u 
    L_0002: localloc 
    L_0004: dup 
    L_0005: ldc.i4.1 
    L_0006: stind.i1 
    L_0007: stloc.0 
    L_0008: ldloc.0 
    L_0009: ldind.u4 
    L_000a: ret 
}

Sanırım bunun bir derleyici hatası olduğunu söylemekten memnuniyet duyarım, ya da en azından: daha önce ".locals init'i yayarlar" demek için istenmeyen bir yan etki ve davranış , özellikle denemek ve stackallocaklı başında tutmak - ama derleyici millet katılıyorum onlara kalmış.

Çözüm şudur: stackallocalanı tanımsız olarak ele alın (adil olmak gerekirse, yapmanız gereken şey budur); sıfırlar olmasını bekliyorsanız: manuel olarak sıfırlayın.


2
Bunun için açık bir bilet var gibi görünüyor . Buna yeni bir yorum ekleyeceğim.
György Kőszeg

Ha, bütün işlerim ve ben ilkinin eksik olduğunu fark etmedim locals init. Güzel bir.
canton7

1
@ canton7 benim gibi bir şeyseniz, otomatik olarak geçmişi atlarsınız .maxstackve .localsorada olduğunu / olmadığını fark etmeyi özellikle kolaylaştırır :)
Marc Gravell

1
The content of the newly allocated memory is undefined.MSDN'ye göre. Spesifikasyon hafızanın da sıfırlanması gerektiğini söylemiyor. Bu yüzden sadece eski çerçevede kazara veya sözleşmesiz bir davranışın sonucu olarak çalışıyor gibi görünüyor.
Luaan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.