C# 是小端序还是大端序?

70

在允许我们通过UDP/IP控制硬件的文档中,我找到了以下片段:

在此通信协议中,DWORD为4字节数据,WORD为2字节数据,BYTE为单字节数据。存储格式为小端序,即4字节(32位)数据存储为:d7-d0,d15-d8,d23-d16,d31-d24;2字节(16位)数据存储为:d7-d0,d15-d8。

我想知道如何将其转换为C#? 我需要在发送之前进行转换吗?例如,如果我要发送32位整数或4个字符的字符串?

6个回答

96

C#本身不定义字节序。但是,每当你进行字节转换时,你都在做出一个选择。然而,BitConverter类具有一个IsLittleEndian字段,告诉你它的行为方式,但是它不提供选择。BinaryReader/BinaryWriter同理。

我的MiscUtil库有一个EndianBitConverter类,允许你定义字节序;对于BinaryReader/Writer也有类似的类。很遗憾没有在线使用指南,但它们非常简单 :)

(EndianBitConverter还有一个常规BitConverter中不具备的功能,即原地执行字节数组中的转换。)


4
同时请记住,在C#中可以直接复制值,例如*ptr = value; 在这种情况下,您应该关注计算机体系结构的字节顺序。 - markmnl
1
通过ReSharper的反编译,我可以看到在我的Windows PC上安装的.Net框架中,BitConverter中的IsLittleEndian属性只是硬编码为返回True。但是,是否存在实际的大端实现.Net的情况呢? - Nyerguds
1
@Nyerguds:在.NET Core中肯定是可行的 - 请参见https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/BitConverter.cs(我不知道我是否曾经在这样的实现上运行过任何东西,但我肯定会避免假设它总是正确的。) - Jon Skeet
1
我认为你的MiscUtil库应该重定向到System.Memory: System.Buffer.Binary.BinaryPrimitives。我相信这些方法将被JIT编译器特殊处理,使用硬件指令,比使用C#代码更快。 - MarkPflug
@Nyerguds 当然是硬编码的,因为它是基于目标机器的字节序的配置常量。在大端序机器上,它将被硬编码为False。 - undefined
@JimBalter 是的,我知道。在其他系统上,它只是以不同的值进行编译。 - undefined

48

您还可以使用

IPAddress.NetworkToHostOrder(...)

简而言之,短整型、整型或长整型。


2
-1 是因为它只适用于大端字节序与小端字节序之间的转换,而原始问题要求将其转换为小端字节序。特别是,该函数仅在小端主机上执行操作(而原始问题不需要),并且在大端主机上不会执行任何操作(而原始问题需要该功能)。 - DarthGizka

12

对于小端模式,简短的回答是“可能不需要,但这取决于你的硬件”。你可以使用以下命令进行检查:

在处理技术方面,小端模式大端模式是指在多字节数据类型的存储方式中,高位字节是否存放在内存的低地址处的一种约定。在小端模式下,低位字节排放在内存的低地址端,而高位字节则排放在内存的高地址端。而大端模式与之相反。

原文中的内容解释了在小端模式下是否需要采取额外措施的问题,并提供了一个检查方法。

bool le = BitConverter.IsLittleEndian;

根据这里所说的内容,你可能需要反转你的缓冲区部分。或者,Jon Skeet在这里有特定的字节顺序转换器(请查找EndianBitConverter)。

请注意,Itanium(例如)是大端的。大多数Intel处理器是小端的。

关于具体的UDP/IP问题...?


有人在 Itanium 上使用 .NET 吗? - SingleNegationElimination
我刚做的基础研究似乎表明Windows始终是小端字节序(Itanium可以支持任一字节序)。因此,除非你在Itanium上运行类似Mono在大端Linux上的奇怪东西,否则BitConverter.IsLittleEndian似乎总是会返回true。请参阅http://blogs.msdn.com/larryosterman/archive/2005/06/07/426334.aspx - piers7
其中一条评论暗示Xbox上的XNA可能是大端字节序;我还没有检查过:http://blogs.msdn.com/larryosterman/archive/2005/06/07/426334.aspx#426591 - Marc Gravell

3
您需要了解网络字节顺序以及CPU的字节序。

通常在TCP/UDP通信中,您需要使用htons函数(以及ntohs和相关函数)将数据转换为网络字节顺序。

通常情况下,网络字节顺序是大端字节序,但在这种情况下(由于某种原因!),通信采用的是小端字节序,因此这些函数并不是非常有用。这很重要,因为您不能假设他们实现的UDP通信遵循任何其他标准,如果您拥有大端架构,那么生活会变得困难,因为您无法像应该做的那样使用htons来封装所有内容 :-(

然而,如果您来自英特尔x86体系结构,则已经是小端字节序,因此只需发送未经转换的数据即可。


1

我正在尝试使用UDP组播中的紧凑数据,并且我需要一些东西来重新排序UInt16八位字节,因为我在数据包头中注意到了一个错误(Wireshark),所以我做了这个:

    private UInt16 swapOctetsUInt16(UInt16 toSwap)
    {
        Int32 tmp = 0;
        tmp = toSwap >> 8;
        tmp = tmp | ((toSwap & 0xff) << 8);
        return (UInt16) tmp;
    }

对于 UInt32,

    private UInt32 swapOctetsUInt32(UInt32 toSwap)
    {
        UInt32 tmp = 0;
        tmp = toSwap >> 24;
        tmp = tmp | ((toSwap & 0xff0000) >> 8);
        tmp = tmp | ((toSwap & 0xff00) << 8);
        tmp = tmp | ((toSwap & 0xff) << 24);
        return tmp;
    }

这只是用于测试

    private void testSwap() {
        UInt16 tmp1 = 0x0a0b;
        UInt32 tmp2 = 0x0a0b0c0d;
        SoapHexBinary shb1 = new SoapHexBinary(BitConverter.GetBytes(tmp1));
        SoapHexBinary shb2 = new SoapHexBinary(BitConverter.GetBytes(swapOctetsUInt16(tmp1)));
        Debug.WriteLine("{0}", shb1.ToString());
        Debug.WriteLine("{0}", shb2.ToString());
        SoapHexBinary shb3 = new SoapHexBinary(BitConverter.GetBytes(tmp2));
        SoapHexBinary shb4 = new SoapHexBinary(BitConverter.GetBytes(swapOctetsUInt32(tmp2)));
        Debug.WriteLine("{0}", shb3.ToString());
        Debug.WriteLine("{0}", shb4.ToString());
    }

这是从哪里输出的内容?
    0B0A: {0}
    0A0B: {0}
    0D0C0B0A: {0}
    0A0B0C0D: {0}

好的代码但有一个bug。tmp = toSwap >> 24; ==> tmp = (toSwap >> 24) & 0xFF; - alsaleem

0
如果您正在解析数据且性能不是关键问题,请考虑使用以下非常简单的代码:
private static byte[] NetworkToHostOrder (byte[] array, int offset, int length)
{
    return array.Skip (offset).Take (length).Reverse ().ToArray ();
}

int foo = BitConverter.ToInt64 (NetworkToHostOrder (queue, 14, 8), 0);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接