Je refactorise mes bibliothèques pour utiliser Span<T>
Pour éviter les allocations de tas si possible, mais comme je cible également des frameworks 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;
}
Chose intéressante, ReinterpretOld
fonctionne bien dans .NET Framework et .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 ReinterpretOld
ne fonctionne-t-il dans les anciens frameworks que par accident et dois-je également appliquer le correctif pour eux?
Remarques:
[MethodImpl(MethodImplOptions.NoInlining)]
à ReinterpretOld
mais cela n'a eu aucun effet.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 a pas de .locals init
, ce qui signifie que stackalloc
se comporte différemment , et n'efface pas l'espace;
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;
}
devient:
.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
}
I pensez 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 le .locals init" , spécifiquement pour essayer de garder stackalloc
sain d'esprit - mais si les gens du compilateur sont d'accord est C'est à eux de voir.
La solution de contournement est la suivante: traitez l'espace stackalloc
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.