Estou refatorando minhas bibliotecas Span<T>
para evitar alocações de heap, se possível, mas como eu viso também estruturas mais antigas, também estou implementando algumas soluções gerais de fallback. Mas agora encontrei um problema estranho e não tenho certeza se encontrei um bug no .NET Core 3 ou estou fazendo algo ilegal.
O problema:
// 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 bem no .NET Framework e no .NET Core 2.0 (para que eu possa ficar feliz com isso, afinal), ainda assim, isso me incomoda um pouco.
Btw. ReinterpretOld
também pode ser corrigido no .NET Core 3.0 com uma pequena modificação:
//return *(uint*)bytes;
uint* asUint = (uint*)bytes;
return *asUint;
Minha pergunta:
Isso é um bug ou ReinterpretOld
funciona em estruturas mais antigas apenas por acidente e devo aplicar a correção também a elas?
Observações:
- A compilação de depuração também funciona no .NET Core 3.0
- Eu tentei aplicar
[MethodImpl(MethodImplOptions.NoInlining)]
paraReinterpretOld
, mas não teve nenhum efeito.
c#
stackalloc
György Kőszeg
fonte
fonte
return Unsafe.As<byte, uint>(ref bytes[0]);
oureturn MemoryMarshal.Cast<byte, uint>(bytes)[0];
- não há necessidade de usarGetPinnableReference()
; olhando para a outra parte, porémSpan<T>
são compiladas para diferentes IL. Não acho que você esteja fazendo nada inválido: suspeito de um bug do JIT.stackalloc
(ou seja, não limpe o espaço alocado)Respostas:
Ooh, este é um achado divertido; o que está acontecendo aqui é que o seu local está sendo otimizado - não há moradores restantes, o que significa que não existe
.locals init
, o que significa questackalloc
se comporta de maneira diferente e não limpa o espaço;torna-se:
Eu acho que eu ficaria feliz em dizer que este é um erro do compilador, ou pelo menos: um efeito colateral e comportamento indesejável, dado que as decisões anteriores foram postas em prática para dizer "emitem as .locals init" , especificamente para tentar mantenha a
stackalloc
sanidade - mas se o pessoal do compilador concorda, é com eles.A solução alternativa é: trate o
stackalloc
espaço como indefinido (que, para ser justo, é o que você deve fazer); se você espera que seja zeros: zere-o manualmente.fonte
locals init
. Agradável..maxstack
e.locals
, tornando-se especialmente fácil não notar que que é / não é lá :)The content of the newly allocated memory is undefined.
de acordo com o MSDN. A especificação também não diz que a memória deve ser zerada. Portanto, parece que só funciona na estrutura antiga por acidente ou como resultado de um comportamento não contratual.