在將泛型列舉轉換為 int時,圍繞 sbyte 型別到位元組的不安全讀取發生了奇怪的事情。
以下示例在 AMD x64 機器上使用 .Net 6.0 進行了測驗。
示例 1:不一致除錯與發布
以下代碼在 Debug 和 Release 模式下生成不同的輸出:
class Program
{
static void Main()
{
byte byteValue = ReadAsByteValue(sbyteValue: -1);
Console.WriteLine(byteValue);
// OUTPUT DEBUG: 255
// OUTPUT RELEASE: -1
}
static unsafe byte ReadAsByteValue(sbyte sbyteValue)
{
return *(byte*)(&sbyteValue);
}
}
由于 typebyte沒有值 -1,我想在發布模式下編譯器回傳sbyte而不是byte.
示例 2A:Release 模式不一致
class Program
{
static void Main()
{
var value1 = GetIntValueEncapsulated((sbyte)-1, true);
var value2 = GetIntValue((sbyte)-1);
Console.WriteLine($"{value1} vs. {value2}");
foreach (var value in Array.Empty<sbyte>())
{
GetIntValueEncapsulated(value, true);
}
// OUTPUT RELEASE: -1 vs. 255
}
static int GetIntValueEncapsulated<T>(T value, bool trueFalse)
where T : unmanaged
{
if (trueFalse)
{
return GetIntValue(value);
}
else
{
throw new NotImplementedException($"Not implemented for size: {Unsafe.SizeOf<T>()}");
}
}
static unsafe int GetIntValue<T>(T value)
where T : unmanaged
{
return *(byte*)(&value);
}
}
示例 2B:注釋掉空的foreach更改結果
var value1 = GetIntValueEncapsulated((sbyte)-1, true);
var value2 = GetIntValue((sbyte)-1);
Console.WriteLine($"{value1} vs. {value2}");
//foreach (var value in Array.Empty<sbyte>())
//{
// GetIntValueEncapsulated(value, true);
//}
// OUTPUT RELEASE: -1 vs. -1
示例 2C:例外行更改結果上的非功能性更改
從示例 2A 開始并替換行:
throw new NotImplementedException($"Not implemented for size: {Unsafe.SizeOf<T>()}");
帶線:
throw new NotImplementedException($"Not implemented for size: " Unsafe.SizeOf<T>());
給出輸出:
// OUTPUT RELEASE: 255 vs. 255
問題
- 這些差異的確切原因是什么?
- 如何強制發布模式下的編譯器按預期運行?(即在除錯模式下)
uj5u.com熱心網友回復:
示例 1:不一致除錯與發布
你應該知道這個例子中編譯器選擇的多載方法是
WriteLine(int). 因此,如果您呼叫WriteLine((uint)byteValue)orWriteLine(byteValue.ToString()),您將得到結果255。編譯器現在更喜歡 32 位有符號整數型別,并且不會因為效率
sbyteValue: -1而編碼。ffffffff000000ff發布模式下優化的副作用。
// Release
ldc.i4.m1
call uint8 C::ReadAsByteValue(int8)
call void [System.Console]System.Console::WriteLine(int32)
// Debug
ldc.i4.m1
call uint8 C::ReadAsByteValue(int8)
stloc.0
ldloc.0
call void [System.Console]System.Console::WriteLine(int32)
您可以在除錯模式下看到,它使用區域變數來傳輸位元組。的檔案stloc說。
存盤到具有小于 4 位元組長的整數值的區域變數中會在該值從堆疊移動到區域變數時截斷該值。
由于釋放模式沒有中間人,沒有截斷,WriteLine方法將按原樣使用ffffffff暫存器中的回傳值。效果也同樣適用short ushort。
示例 2A:Release 模式不一致
根據上面的解釋,從暫存器回傳GetIntValueEncapsulated或GetIntValue在暫存器中回傳的值總是ffffffff.
對不起,我不是 JIT 專家,所以我無法說出實作細節。我知道這是由方法行內引起的。應用于NoInlining該方法,輸出為-1。
[MethodImpl(MethodImplOptions.NoInlining)]
static unsafe int GetIntValue<T>(T value)
下面的代碼可以用來模擬強制行內的效果。
sbyte a = -1;
var value2 = *(byte*)(&a);
當方法行內時,編譯器使用以下工具設定 value2 的值,強制型別為位元組。
movzx edi,byte ptr [rsp 4Ch]
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/498413.html
上一篇:添加對單個DLL檔案的參考
下一篇:使用GoogleApps腳本添加/更新Google作業表行資料比較HTML表單中的值-嘗試在作業表上查找匹配項并更新所選行資料
