为什么不安全阅读sbyte->字节在发布模式中不一致: *(字节 *)(& sbytevalue)?

发布于 2025-02-12 17:15:16 字数 2415 浏览 0 评论 0原文

通用枚举奇怪的事情发生在不安全的情况下,奇怪的事情发生在byte上。

在AMD X64机器上使用.NET 6.0测试了Folloging示例。

示例1:不一致的调试与发行版

以下代码在调试和发布模式下生成不同的输出:

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);
    }
}

因为类型byte没有值-1,我想在版本中,模式编译器返回sbyte而不是byte

示例2a:发行模式中的不一致

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

问题

  • 这些差异的确切原因是什么?
  • 如何在发布模式下强制编译器以预期的方式行事? (即在调试模式下)

While writing conversion of generic enum to int strange things happened around unsafe read of sbyte type to byte.

The folloging examples were tested with .Net 6.0 on AMD x64 machine.

Example 1: Inconsistency Debug vs. Release

The following code generates different output in Debug and in Release mode:

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);
    }
}

Since type byte does not have value -1, I suppose that in Release mode the compiler returns sbyte instead of byte.

Example 2A: Inconsistency in Release mode

class Program
{
    static void Main()
    {
        var value1 = GetIntValueEncapsulated((sbyte)-1, true);
        var value2 = GetIntValue((sbyte)-1);

        Console.WriteLine(
quot;{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(
quot;Not implemented for size: {Unsafe.SizeOf<T>()}");
        }
    }

    static unsafe int GetIntValue<T>(T value)
        where T : unmanaged
    {
        return *(byte*)(&value);
    }
}

Example 2B: Commenting out empty foreach changes results

var value1 = GetIntValueEncapsulated((sbyte)-1, true);
var value2 = GetIntValue((sbyte)-1);

Console.WriteLine(
quot;{value1} vs. {value2}");

//foreach (var value in Array.Empty<sbyte>())
//{
//    GetIntValueEncapsulated(value, true);
//}

// OUTPUT RELEASE: -1 vs. -1

Example 2C: Non-functional change on the Exception line changes results

Starting with Example 2A and replacing line:

throw new NotImplementedException(
quot;Not implemented for size: {Unsafe.SizeOf<T>()}"); 

with line:

throw new NotImplementedException(
quot;Not implemented for size: " + Unsafe.SizeOf<T>()); 

gives output:

// OUTPUT RELEASE: 255 vs. 255

Questions

  • What is the exact cause of these differences?
  • How to force compiler in the Release mode to behave as expected? (i.e. as in the Debug mode)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

夏见 2025-02-19 17:15:16

示例1:不一致的调试与发行版

  1. 您应该知道编译器在此示例中选择的过载方法是writeline(int)。因此,如果您调用writeline(((UINT)字节)writeline(bytevalue.tostring()),您将获得结果255

  2. 今天的编译器更喜欢32位签名的整数类型,并将编码sbytevalue:-1 ffffffff而不是000000FFF,因为效率。


  3. 在发行模式下优化的副作用。

// 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方法将在寄存器中使用返回值fffffffff。该效果也适用于短+ushort,其原因相同。


示例2a:发行模式中的不一致

根据上述解释,从getIntValueEncapsulated返回的值getIntValue emboister中的始终是ffffffffff < /代码>。

抱歉,我不是JIT专家,所以我无法说明实现细节。我所知道的是这是由方法内在引起的。将noinlining应用于该方法,输出为-1。

[MethodImpl(MethodImplOptions.NoInlining)]
static unsafe int GetIntValue<T>(T value)

以下代码可用于模拟强制内部的效果。

sbyte a = -1;
var value2 = *(byte*)(&a);

当该方法内联时,编译器使用以下仪器来设置迫使字节类型的值2的值。

movzx       edi,byte ptr [rsp+4Ch]

执行预期结果

  1. 使用确定性方法。
Console.WriteLine((uint)byteValue); // Console.WriteLine(uint)
Console.WriteLine(byteValue.ToString()); // byte.ToString()
  1. 转换为uint*首先
static unsafe byte ReadAsByteValue(sbyte sbyteValue)
    => (byte)*(uint*)(&sbyteValue);
static unsafe int GetIntValue<T>(T value) where T : unmanaged
    => (byte)*(uint*)(&value);

Example 1: Inconsistency Debug vs. Release

  1. You should know that the overload method chosen by the compiler in this example is WriteLine(int). So if you call WriteLine((uint)byteValue) or WriteLine(byteValue.ToString()), you'll get the result 255.

  2. The compiler prefers 32-bit signed integer types today and will encode sbyteValue: -1 to ffffffff not 000000ff because of the efficiency.

  3. The side-effect of optimzation in release mode.

// 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)

You can see in debug mode, it uses a local variable to transmit the byte. The docs of stloc says.

Storing into locals that hold an integer value smaller than 4 bytes long truncates the value as it moves from the stack to the local variable.

Since there is no middleman in release mode, no truncation, WriteLine method will use the return value ffffffff in the register as is. The effect also applies to short+ushort with the same reason.


Example 2A: Inconsistency in Release mode

According to the explaination above the values returned from GetIntValueEncapsulated or GetIntValue in the registers are always ffffffff.

Sorry I'm not a JIT expert, so I can't tell the implemention detail. What I know is this is caused by method inlining. Apply NoInlining to the method, the output is -1.

[MethodImpl(MethodImplOptions.NoInlining)]
static unsafe int GetIntValue<T>(T value)

The following code can be used to simulate the effect of forced inlining.

sbyte a = -1;
var value2 = *(byte*)(&a);

When the method is inline the compiler uses the following instrument to set the value of value2 which force the type to byte.

movzx       edi,byte ptr [rsp+4Ch]

To enforce the expected result

  1. Use a deterministic method.
Console.WriteLine((uint)byteValue); // Console.WriteLine(uint)
Console.WriteLine(byteValue.ToString()); // byte.ToString()
  1. Convert to uint* first
static unsafe byte ReadAsByteValue(sbyte sbyteValue)
    => (byte)*(uint*)(&sbyteValue);
static unsafe int GetIntValue<T>(T value) where T : unmanaged
    => (byte)*(uint*)(&value);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文