这个不安全的代码也应该在 .NET Core 3 中工作吗?
Should this unsafe code work also in .NET Core 3?
我正在重构我的库以使用 Span<T>
来尽可能避免堆分配,但由于我还针对较旧的框架,因此我也在实施一些通用的后备解决方案。但是现在我发现了一个奇怪的问题,我不太确定我是在 .NET Core 3 中发现了一个错误,还是我在做一些非法的事情。
问题:
// 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;
}
有趣的是,ReinterpretOld
在 .NET Framework 和 .NET Core 2.0 中运行良好(所以我对它很满意),但它还是让我有点困扰。
顺便说一句。 ReinterpretOld
也可以在 .NET Core 3.0 中通过一个小的修改来修复:
//return *(uint*)bytes;
uint* asUint = (uint*)bytes;
return *asUint;
我的问题:
这是一个错误还是 ReinterpretOld
在旧框架中工作只是偶然,我应该为它们应用修复程序吗?
备注:
- 调试版本也适用于 .NET Core 3.0
- 我尝试将
[MethodImpl(MethodImplOptions.NoInlining)]
应用到 ReinterpretOld
但没有效果。
哦,这是一个有趣的发现;这里发生的是你的本地人正在优化 - 没有本地人剩余,这意味着没有 .locals init
,这意味着 stackalloc
的行为 不同 ,并且不擦除 space;
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;
}
变为:
.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
}
我 认为 我很高兴地说这是一个编译器错误,或者至少:考虑到 previous decisions have been put in place to say "emit the .locals init",这是一个不受欢迎的副作用和行为, 特别是 试图保持 stackalloc
理智 - 但编译器人员是否同意取决于他们。
解决方法是:将 stackalloc
space 视为未定义(公平地说,这就是您的本意);如果您希望它为零:手动将其归零。
我正在重构我的库以使用 Span<T>
来尽可能避免堆分配,但由于我还针对较旧的框架,因此我也在实施一些通用的后备解决方案。但是现在我发现了一个奇怪的问题,我不太确定我是在 .NET Core 3 中发现了一个错误,还是我在做一些非法的事情。
问题:
// 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;
}
有趣的是,ReinterpretOld
在 .NET Framework 和 .NET Core 2.0 中运行良好(所以我对它很满意),但它还是让我有点困扰。
顺便说一句。 ReinterpretOld
也可以在 .NET Core 3.0 中通过一个小的修改来修复:
//return *(uint*)bytes;
uint* asUint = (uint*)bytes;
return *asUint;
我的问题:
这是一个错误还是 ReinterpretOld
在旧框架中工作只是偶然,我应该为它们应用修复程序吗?
备注:
- 调试版本也适用于 .NET Core 3.0
- 我尝试将
[MethodImpl(MethodImplOptions.NoInlining)]
应用到ReinterpretOld
但没有效果。
哦,这是一个有趣的发现;这里发生的是你的本地人正在优化 - 没有本地人剩余,这意味着没有 .locals init
,这意味着 stackalloc
的行为 不同 ,并且不擦除 space;
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;
}
变为:
.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
}
我 认为 我很高兴地说这是一个编译器错误,或者至少:考虑到 previous decisions have been put in place to say "emit the .locals init",这是一个不受欢迎的副作用和行为, 特别是 试图保持 stackalloc
理智 - 但编译器人员是否同意取决于他们。
解决方法是:将 stackalloc
space 视为未定义(公平地说,这就是您的本意);如果您希望它为零:手动将其归零。