Refaktoryzuję swoje biblioteki, aby Span<T>
w miarę możliwości unikały alokacji sterty, ale ponieważ celuję również w starsze platformy, wdrażam również ogólne rozwiązania awaryjne. Ale teraz znalazłem dziwny problem i nie jestem do końca pewien, czy znalazłem błąd w .NET Core 3, czy robię coś nielegalnego.
Problem:
// 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;
}
Co ciekawe, ReinterpretOld
działa dobrze w .NET Framework i .NET Core 2.0 (więc w końcu mógłbym być z tego zadowolony), ale trochę mnie to niepokoi.
Btw. ReinterpretOld
można naprawić również w .NET Core 3.0 przez niewielką modyfikację:
//return *(uint*)bytes;
uint* asUint = (uint*)bytes;
return *asUint;
Moje pytanie:
Czy to błąd, czy ReinterpretOld
działa w starszych frameworkach tylko przypadkowo i czy powinienem zastosować poprawkę również dla nich?
Uwagi:
- Kompilacja debugowania działa również w .NET Core 3.0
- Starałem się stosować
[MethodImpl(MethodImplOptions.NoInlining)]
doReinterpretOld
ale to nie miało żadnego wpływu.
c#
stackalloc
György Kőszeg
źródło
źródło
return Unsafe.As<byte, uint>(ref bytes[0]);
lubreturn MemoryMarshal.Cast<byte, uint>(bytes)[0];
- nie ma potrzeby korzystaniaGetPinnableReference()
; spoglądając w drugą stronęSpan<T>
, kompilują się do różnych IL. Nie sądzę, że robisz coś nie tak: Podejrzewam błąd JIT.stackalloc
(tzn. nie usuwa przydzielonego miejsca)Odpowiedzi:
Och, to zabawne znalezisko; dzieje się tutaj to, że twój lokal się optymalizuje - nie ma już miejscowych, co oznacza, że nie ma
.locals init
, co oznacza, żestackalloc
zachowuje się inaczej i nie czyści przestrzeni;staje się:
I pomyśleć , że będę szczęśliwy mogąc powiedzieć, że jest to błąd kompilatora, lub co najmniej: niepożądanym efektem ubocznym i zachowanie zważywszy, że wcześniejsze decyzje zostały wprowadzone, aby powiedzieć „emitują .locals init” , specjalnie , aby spróbować zachowaj
stackalloc
rozsądek - ale to, czy ludzie kompilatora się zgodzą, zależy od nich.Obejście polega na: traktowaniu
stackalloc
przestrzeni jako niezdefiniowanej (co, uczciwie mówiąc, to jest to, co masz zrobić); jeśli spodziewasz się, że będzie to zero: wyzeruj go ręcznie.źródło
locals init
. Niezłe..maxstack
i.locals
, dzięki czemu szczególnie łatwo nie zauważyć, że tam jest / nie ma :)The content of the newly allocated memory is undefined.
zgodnie z MSDN. Specyfikacja nie mówi też, że pamięć powinna być zerowana. Wygląda więc na to, że działa tylko na starych ramach przez przypadek lub w wyniku działań pozaumownych.