Je refactorise mes bibliothèques Span<T>
pour éviter les allocations de tas si possible, mais comme je cible également des cadres plus anciens, j'implémente également des solutions de secours générales. Mais maintenant, j'ai trouvé un problème étrange et je ne sais pas trop si j'ai trouvé un bogue dans .NET Core 3 ou si je fais quelque chose d'illégal.
Le problème:
// 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;
}
Il est intéressant de noter que cela ReinterpretOld
fonctionne bien dans .NET Framework et dans .NET Core 2.0 (donc je pourrais en être satisfait après tout), mais cela me dérange un peu.
Btw. ReinterpretOld
peut également être corrigé dans .NET Core 3.0 par une petite modification:
//return *(uint*)bytes;
uint* asUint = (uint*)bytes;
return *asUint;
Ma question:
Est-ce un bogue ou ne ReinterpretOld
fonctionne- t-il que par accident dans les anciens frameworks et dois-je également appliquer le correctif pour eux?
Remarques:
- La version de débogage fonctionne également dans .NET Core 3.0
- J'ai essayé d'appliquer
[MethodImpl(MethodImplOptions.NoInlining)]
àReinterpretOld
mais il n'a eu aucun effet.
la source
return Unsafe.As<byte, uint>(ref bytes[0]);
oureturn MemoryMarshal.Cast<byte, uint>(bytes)[0];
- pas besoin d'utiliserGetPinnableReference()
; en regardant l'autre, cependantSpan<T>
compilent en IL différent. Je ne pense pas que vous fassiez quelque chose d'invalide: je soupçonne un bogue JIT.stackalloc
(c'est-à-dire qu'il n'efface pas l'espace alloué)Réponses:
Ooh, c'est une trouvaille amusante; ce qui se passe ici, c'est que votre section locale est optimisée - il n'y a plus de section locale, ce qui signifie qu'il n'y en a pas
.locals init
, ce qui signifie que lestackalloc
comportement est différent et n'efface pas l'espace;devient:
Je pense que je serais heureux de dire qu'il s'agit d'un bogue du compilateur, ou du moins: un effet secondaire et un comportement indésirables étant donné que des décisions antérieures ont été mises en place pour dire "émettre l'initialisation .locals" , en particulier pour essayer de rester
stackalloc
sain d'esprit - mais si les gens du compilateur sont d'accord, c'est à eux de décider.La solution de contournement est la suivante: traiter l'
stackalloc
espace comme indéfini (ce qui, pour être juste, est ce que vous êtes censé faire); si vous pensez qu'il s'agit de zéros: mettez-le à zéro manuellement.la source
locals init
. Joli..maxstack
et.locals
, ce qui rend particulièrement facile de ne pas remarquer qu'il est / n'est pas là :)The content of the newly allocated memory is undefined.
selon MSDN. La spécification ne dit pas non plus que la mémoire doit être mise à zéro. Il semble donc que cela ne fonctionne que sur un ancien cadre par accident ou à la suite d'un comportement non contractuel.