【问题标题】:C# signed/unsigned cast issuesC# 有符号/无符号转换问题
【发布时间】:2015-10-27 13:29:29
【问题描述】:

我需要在有符号整数和它们作为字节序列的内部表示之间进行转换。在 C 中,我使用的函数如下:

unsigned char hibyte(unsigned short i)
{return i>>8;}

unsigned char lobyte(unsigned short i)
{return i & 0xFF;}

unsigned short makeshort(unsigned char hb, unsigned char lb)
{return ((short)hb << 8) | (short)lb;}

问题是这段代码在 C# 下不起作用,因为有符号/无符号转换的规则不一样:据我所知,C# 转换意味着值的转换,而在 C 中,有符号/无符号类型之间的转换不会修改基础数据。此外,在 C# 中,对于有符号数,>> 运算符会移入符号位。所有这一切都让我的代码难以转换为 C#,例如

1) C# 函数

public static byte hibyte(short i)
{return (byte) (i>>8);}

如果 i 为负数则抛出溢出异常

2) C#函数

public static ushort makeshort(byte hb, byte lb)
{return (short) (((ushort)hb << 8) | (ushort)lb); }

如果得到的 short 为负数,则抛出溢出异常。这里 表达式 "(ushort)hb

【问题讨论】:

  • 可能只是将代码包装在unchecked { ... } 中的一个简单案例,这会抑制溢出检查。
  • 你看过BitConverter类吗?
  • ushort 中的public static ushort makeshort 错字吗?

标签: c#


【解决方案1】:

几个答案已经注意到BitConverter 类,以及使用unchecked 进行位移和强制转换。我将快速演示第三个选项:“C 风格的联合结构”。

[StructLayout(LayoutKind.Explicit)]
struct Converter
{
   [FieldOffset(0)]
   public ushort UshortValue;
   [FieldOffset(0)]
   public short ShortValue;
   [FieldOffset(0)]
   public byte LoByte;    
   [FieldOffset(1)]
   public byte HiByte;
}

然后像这样使用。

ushort test1 = new Converter { ShortValue = -123 }.UshortValue; // 65413
ushort test2 = new Converter { HiByte = 1, LoByte = 100 }.UshortValue; // 356
byte test3 = new Converter { UshortValue = 356 }.LoByte;  // 100

它比BitConverter 的优势在于您不需要分配临时字节数组。

【讨论】:

    【解决方案2】:

    您可以使用 BitConverter 类来代替:

    short x = 1;
    byte[] bytes = BitConverter.GetBytes(x);
    short y = BitConverter.ToInt32(bytes, 0);
    

    这对其他整数类型 intlong 也有重载。

    如果你真的想自己写代码,可以像这样指定unchecked来避免溢出异常:

    public static byte hibyte(short i)
    {
        unchecked
        {
            return (byte)(i >> 8);
        }
    }
    
    public static ushort makeushort(byte hb, byte lb)
    {
        unchecked
        {
            return (ushort)((hb << 8) | lb);
        }
    }
    
    public static short makeshort(byte hb, byte lb)
    {
        unchecked
        {
            return (short)((hb << 8) | lb);
        }
    }
    

    不过,我只会使用BitConverter;它非常快。但是请注意,它始终使用运行代码的机器的字节序。

    这是通过BitConverter.IsLittleEndian 报告的。

    如果您要转换的数据具有不同的字节序,您必须自己进行。

    【讨论】:

    • 得益于its internal use of unsafe code,BitConverter 甚至可能更快。
    • @ScottChamberlain 当值正确对齐时尤其如此 - 在这种情况下,它只不过是带有副本的简单“重新解释演员”。但是,必须注意的是,它将始终使用机器的字节顺序,这对于交换数据格式不是很有用。
    • @Luaan 这是一个很好的观点(字节序),所以我会将它添加到答案中。
    • 非常感谢,unchecked 和 BitConvert 都能满足我的要求,这就是我想要的。保罗
    【解决方案3】:

    正如其他人所提到的,您可以使用 BitConverter 类,尽管他们未能在实际代码中考虑字节序(只是在最后简要提及):

    public static (byte Hibyte, byte Lobyte) GetBytes(short i)
    {   // This is my recommendation; it gets both bytes in one call, so it
        // may be more efficient.
        var bytes = BitConverter.GetBytes(i);
        if (BitConverter.IsLittleEndian)
            Array.Reverse(bytes)
        return (bytes[0], bytes[1]);
    }
    
    public static (byte Hibyte, byte Lobyte) GetBytes(ushort i)
    {   // BitConverter works equally well with unsigned types.
        var bytes = BitConverter.GetBytes(i);
        if (BitConverter.IsLittleEndian)
            Array.Reverse(bytes)
        return (bytes[0], bytes[1]);
    }
    
    public static byte Hibyte(short i)
    {   // If you want to use your original schema, here's the hi byte:
        if (BitConverter.IsLittleEndian)
            return BitConverter.GetBytes()[1];
        return BitConverter.GetBytes()[0];
    }
    
    public static byte Hibyte(ushort i)
    {   // Again, same thing works for ushort
        if (BitConverter.IsLittleEndian)
            return BitConverter.GetBytes()[1];
        return BitConverter.GetBytes()[0];
    }
    
    public static short MakeShort(byte hb, byte lb)
    {
        byte[] bytes = new byte[] { hb, lb };
        if (BitConverter.IsLittleEndian)
            Array.Reverse(bytes);
        return BitConverter.ToInt16(bytes);
    }
    
    public static ushort MakeUShort(byte hb, byte lb)
    {
        byte[] bytes = new byte[] { hb, lb };
        if (BitConverter.IsLittleEndian)
            Array.Reverse(bytes);
        return BitConverter.ToUInt16(bytes);
    }
    

    而且,虽然其他人提到使用 unchecked,但他们忽略了一个问题:正如您在原始问题中指出的那样,当对有符号整数类型进行右移时,符号位会重复,所以虽然这有效:

    public static byte Hibyte(ushort i)
    { return (byte)(i >> 8); }
    // No need for unchecked, because result will always fit in one byte and is never < 0.
    
    public static byte Lobyte(ushort i)
    { return (byte)(i & 0xFF); } // No need for unchecked for the same reason.
    
    public static byte LoByte(short i)
    { return (byte)(i & 0xFF); } 
    // Like above, no need for unchecked for the same reasons; also, bitwise & works the same 
    // for both signed and unsigned types.
    
    public static ushort MakeUShort(byte hb, byte lb)
    { return (ushort)((hb << 8) | lb); } 
    // Again, no need for unchecked; result is always 16 bits and never negative.
    
    public static short MakeShort(byte hb, byte lb)
    { unchecked { return (short)((hb << 8) | lb); } }
    // This time, we may need unchecked because result may overflow short.
    

    ...为 signed short 的高字节给出的代码需要额外的转换:

    public static byte Hibyte(short i)
    { unchecked { return (byte)((ushort)i >> 8); } }
    // Again, the unchecked is needed, this time because i may be negative, 
    // which may need to be accounted for when casting to a ushort.
    

    通过在移位之前将有符号的short 转换为无符号的ushort,我们可以防止符号位被添加到前面 8 次。或者,我们可以使用按位 &amp; 来忽略传播的符号位:

    public static byte HiByte (short i)
    { return (byte)((i >> 8) & 0xFF); }
    // Since bitwise operations never result in overflow, and by the time we cast at the end, 
    // the number is guaranteed to fit in a byte and be >= 0, we no longer need any unchecked blocks.
    

    其中任何一个都会产生相同的结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-19
      • 2013-07-23
      • 2012-11-18
      • 2011-10-25
      • 2013-04-06
      • 1970-01-01
      • 2013-01-22
      • 1970-01-01
      相关资源
      最近更新 更多