【问题标题】:Possibly incorrect IL gets emitted when using a combination of unsafe statement and a branch使用不安全语句和分支的组合时,可能会发出不正确的 IL
【发布时间】:2014-10-03 17:11:42
【问题描述】:

这是一个非常简单的 C# 控制台应用程序/Framework 4.5,并使用 Visual Studio 2013 Update 2 编译。它从字节数组中读取双精度,并预先进行简单的范围检查。

class Program
{
    private static byte[] fData = new byte[8];

    public static unsafe double ReadDouble(int offset)
    {
        if ((offset + 8) > fData.Length)
            return 0;

        double value = 0;
        fixed (byte* pBuffer = fData)
            value = *((double*)(pBuffer));

        Console.WriteLine("This code is never reached!");
        return value;
    }

    static void Main(string[] args)
    {
        fData = BitConverter.GetBytes(1234.56789d);

        double result = ReadDouble(0);

        Console.WriteLine("result: {0}", result);
        Console.ReadKey();
    }
}

在没有 /optimize 标志的情况下编译时,任何 CPU 配置都会输出以下内容(并且生成的 IL 似乎有点狡猾):

result: 0

当使用 /optimize 标志编译时,它会正确输出:

This code is never reached!
result: 1234,56789

关于为什么会这样的任何想法?

PS:如果范围检查被注释掉(if((offset ...)),那么它会再次输出正确的结果。

编辑:这是为 ReadDouble() 函数生成的 IL,请注意缺少 Console.WriteLine

.method public hidebysig static 
    float64 ReadDouble (
        int32 offset
    ) cil managed 
{
    // Method begins at RVA 0x2060
    // Code size 81 (0x51)
    .maxstack 2
    .locals init (
        [0] float64 'value',
        [1] bool,
        [2] float64,
        [3] uint8& pinned pBuffer,
        [4] uint8[]
    )

    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldc.i4.8
    IL_0003: add
    IL_0004: ldsfld uint8[] WeirdBug.Program::fData
    IL_0009: ldlen
    IL_000a: conv.i4
    IL_000b: cgt
    IL_000d: ldc.i4.0
    IL_000e: ceq
    IL_0010: stloc.1
    IL_0011: ldloc.1
    IL_0012: brtrue.s IL_0020

    IL_0014: ldc.r8 0.0
    IL_001d: stloc.2
    IL_001e: br.s IL_004f

    IL_0020: ldc.r8 0.0
    IL_0029: stloc.0
    IL_002a: ldsfld uint8[] WeirdBug.Program::fData
    IL_002f: dup
    IL_0030: stloc.s 4
    IL_0032: brfalse.s IL_003a

    IL_0034: ldloc.s 4
    IL_0036: ldlen
    IL_0037: conv.i4
    IL_0038: brtrue.s IL_003f

    IL_003a: ldc.i4.0
    IL_003b: conv.u
    IL_003c: stloc.3
    IL_003d: br.s IL_0048

    IL_003f: ldloc.s 4
    IL_0041: ldc.i4.0
    IL_0042: ldelema [mscorlib]System.Byte
    IL_0047: stloc.3

    IL_0048: ldloc.3
    IL_0049: conv.i
    IL_004a: ldind.r8
    IL_004b: stloc.0
    IL_004c: ldc.i4.0
    IL_004d: conv.u
    IL_004e: stloc.3

    IL_004f: ldloc.2
    IL_0050: ret
}

【问题讨论】:

  • 注意fData = new byte[8]中创建的数组被fData = BitConverter.GetBytes(1234.56789d);扔掉了
  • 如果您认为您的 MSIL 不可靠,请展示它....
  • @AdrianoRepetti 十进制的后缀是 m。 double 的后缀是 d。
  • 天哪,你是对的。是时候出去散步了……
  • 在您的 MSIL 中,有 两个 类型为 double 的局部变量,一个是返回值,而 value 是另一个。你确定这来自同一版本的源代码?

标签: c# unsafe


【解决方案1】:

您可能遇到错位问题。一些英特尔浮点指令可以为您修复错位,而一些较新的(SIMD 指令)则不会。

您可能应该为此坚持使用BitConverter 课程。

另一种选择是使用不安全代码进行转换的另一个方向——实际数组将是double[],您可以将double* 转换为byte*。一般来说,将T* 转换为byte* 是安全的,反之则不然。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多