Estoy refactorizando mis bibliotecas para usarlas Span<T>
para evitar las asignaciones de almacenamiento dinámico, si es posible, pero al apuntar también a marcos más antiguos, también estoy implementando algunas soluciones de respaldo generales. Pero ahora encontré un problema extraño y no estoy muy seguro de si encontré un error en .NET Core 3 o si estoy haciendo algo ilegal.
La cuestión:
// 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;
}
Curiosamente, ReinterpretOld
funciona bien en .NET Framework y en .NET Core 2.0 (por lo que podría estar contento con él después de todo), aún así, me molesta un poco.
Por cierto. ReinterpretOld
se puede arreglar también en .NET Core 3.0 mediante una pequeña modificación:
//return *(uint*)bytes;
uint* asUint = (uint*)bytes;
return *asUint;
Mi pregunta:
¿Es esto un error o ReinterpretOld
funciona en marcos anteriores solo por accidente y debo aplicar la solución también para ellos?
Observaciones:
- La compilación de depuración también funciona en .NET Core 3.0
- I intentado aplicar
[MethodImpl(MethodImplOptions.NoInlining)]
aReinterpretOld
pero no tuvo ningún efecto.
fuente
return Unsafe.As<byte, uint>(ref bytes[0]);
oreturn MemoryMarshal.Cast<byte, uint>(bytes)[0];
- no es necesario usarGetPinnableReference()
; sin embargo, mirando a la otra parteSpan<T>
compilan en diferentes IL. No creo que estés haciendo nada inválido: sospecho que hay un error JIT.stackalloc
(es decir, no borra el espacio asignado)Respuestas:
Ooh, este es un hallazgo divertido; Lo que está sucediendo aquí es que su local se está optimizando: no quedan locales, lo que significa que no existe
.locals init
, lo que significa que sestackalloc
comporta de manera diferente y no borra el espacio;se convierte en:
Creo que me alegraría decir que se trata de un error del compilador, o al menos: un efecto secundario y un comportamiento indeseables dado que se han tomado decisiones previas para decir "emitir el .locals init" , específicamente para intentar manténgase
stackalloc
cuerdo, pero si la gente del compilador está de acuerdo depende de ellos.La solución es: tratar el
stackalloc
espacio como indefinido (que, para ser justos, es lo que debe hacer); si espera que sean ceros: ponga a cero manualmente.fuente
locals init
. Buena esa..maxstack
y.locals
, haciendo que sea especialmente fácil no notar que está / no está allí :)The content of the newly allocated memory is undefined.
De acuerdo con MSDN. La especificación tampoco dice que la memoria deba ponerse a cero. Por lo tanto, parece que solo funciona en un marco antiguo por accidente o como resultado de un comportamiento no contractual.