背景

最开始接触c语言时,总会花很多精力去记各种操作符的优先级。后来才发现,实际开发根本就不用记。自此这么多年,除了加减乘除这种小学生都知道的优先级,我基本都是用括号搞定优先级。

同样,不同语言的字节序都不尽相同,比如c++大部分平台是小端存储的(c++的字节序与处理器有关系,具体大小端与编译器对应关系见参考文档1),java就是采用大端存储,c#似乎是小端存储(c#我写的少,有心人可以帮忙确认下)。在写跨语言或者跨平台的程序时,字节序的转换就不得不考虑了。

大端小端转换

关于大端小端的由来,可以看看《格列佛游记》,我就不转载了。
大端:数据的高位存储在低地址中,低位存储在高地址中。
小端:数据的低位存储在高地址中,高位存储在低地址中。

举个例子,数据0x12345678,占4个字节,分别是0x12、0x34、0x56、0x78。假设内存起始地址是0x80000000。
对于小端程序来说,0x80000000存放0x78,0x80000001存放0x56,0x80000002存放0x34,0x80000003存放0x12。
而对于大端程序来说,0x80000000存放0x12,0x80000001存放0x34,0x80000002存放0x56,0x80000003存放0x78。

这里要说明的是,大小端只对多字节数据类型有影响,对于单字节类型的数组、字符串,并没有什么影响。另外如果是多个多字节数据放在一起,顺序依然不会变,只是每个多字节数据区分大端还是小端罢了。

所以,一般情况下,大小端字节序转换规则如下:

单字节数据或数组,不需要转换
少于或等于4字节的长整型,前后互换就行了。
还有三种特殊情况:

  • 对于8字节的长整型int64来说,并不是前后字节互换,而是先将高32位和低32位互换,然后,将这两个int32内部字节位互换。
  • 对于float类型来说,同样是前后字节对换。
  • 尽管double是8字节的,但是其转换方式和float一样,也是前后字节兑换。

具体代码可参考poco,我就不贴出来了,参阅参考文献2。

怎么判断大端还是小端

可能很多毕业生找工作时都会被问到如何判断当前系统是大端还是小端。知道原理后,实现起来其实很简单。定义一个多字节类型,然后判断不同地址的值,看看高地址存放的是高位数据还是低位数据,就能轻易区分当前是大端还是小端了。

实际工作中,就没必要这样判断了。因为不同平台上大小端是提前就知道的。如果使用poco库,完成可以依赖其Platform.h中的宏定义,里面还包含了我没有见过的平台。

#if defined(__ALPHA) || defined(__alpha) || defined(__alpha__) || defined(_M_ALPHA)
	#define POCO_ARCH POCO_ARCH_ALPHA
	#define POCO_ARCH_LITTLE_ENDIAN 1
#elif defined(i386) || defined(__i386) || defined(__i386__) || defined(_M_IX86) || defined(EMSCRIPTEN) || defined(__EMSCRIPTEN__)
	#define POCO_ARCH POCO_ARCH_IA32
	#define POCO_ARCH_LITTLE_ENDIAN 1
#elif defined(_IA64) || defined(__IA64__) || defined(__ia64__) || defined(__ia64) || defined(_M_IA64)
	#define POCO_ARCH POCO_ARCH_IA64
	#if defined(hpux) || defined(_hpux)
		#define POCO_ARCH_BIG_ENDIAN 1
	#else
		#define POCO_ARCH_LITTLE_ENDIAN 1
	#endif
#elif defined(__x86_64__) || defined(_M_X64)
	#define POCO_ARCH POCO_ARCH_AMD64
	#define POCO_ARCH_LITTLE_ENDIAN 1
#elif defined(__mips__) || defined(__mips) || defined(__MIPS__) || defined(_M_MRX000)
	#define POCO_ARCH POCO_ARCH_MIPS
	#if defined(POCO_OS_FAMILY_WINDOWS)
		// Is this OK? Supports windows only little endian??
		#define POCO_ARCH_LITTLE_ENDIAN 1
	#elif defined(__MIPSEB__) || defined(_MIPSEB) || defined(__MIPSEB)
		#define POCO_ARCH_BIG_ENDIAN 1
	#elif defined(__MIPSEL__) || defined(_MIPSEL) || defined(__MIPSEL)
		#define POCO_ARCH_LITTLE_ENDIAN 1
	#else
		#error "MIPS but neither MIPSEL nor MIPSEB?"
	#endif
#elif defined(__hppa) || defined(__hppa__)
	#define POCO_ARCH POCO_ARCH_HPPA
	#define POCO_ARCH_BIG_ENDIAN 1
#elif defined(__PPC) || defined(__POWERPC__) || defined(__powerpc) || defined(__PPC__) || \
      defined(__powerpc__) || defined(__ppc__) || defined(__ppc) || defined(_ARCH_PPC) || defined(_M_PPC)
	#define POCO_ARCH POCO_ARCH_PPC
	#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
		#define POCO_ARCH_LITTLE_ENDIAN 1
	#else
		#define POCO_ARCH_BIG_ENDIAN 1
	#endif
#elif defined(_POWER) || defined(_ARCH_PWR) || defined(_ARCH_PWR2) || defined(_ARCH_PWR3) || \
      defined(_ARCH_PWR4) || defined(__THW_RS6000)
	#define POCO_ARCH POCO_ARCH_POWER
	#define POCO_ARCH_BIG_ENDIAN 1
#elif defined(__sparc__) || defined(__sparc) || defined(sparc)
	#define POCO_ARCH POCO_ARCH_SPARC
	#define POCO_ARCH_BIG_ENDIAN 1
#elif defined(__arm__) || defined(__arm) || defined(ARM) || defined(_ARM_) || defined(__ARM__) || defined(_M_ARM)
	#define POCO_ARCH POCO_ARCH_ARM
	#if defined(__ARMEB__)
		#define POCO_ARCH_BIG_ENDIAN 1
	#else
		#define POCO_ARCH_LITTLE_ENDIAN 1
	#endif
#elif defined(__arm64__) || defined(__arm64)
	#define POCO_ARCH POCO_ARCH_ARM64
	#if defined(__ARMEB__)
		#define POCO_ARCH_BIG_ENDIAN 1
	#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
		#define POCO_ARCH_BIG_ENDIAN 1
	#else
		#define POCO_ARCH_LITTLE_ENDIAN 1
	#endif
#elif defined(__m68k__)
	#define POCO_ARCH POCO_ARCH_M68K
	#define POCO_ARCH_BIG_ENDIAN 1
#elif defined(__s390__)
	#define POCO_ARCH POCO_ARCH_S390
	#define POCO_ARCH_BIG_ENDIAN 1
#elif defined(__sh__) || defined(__sh) || defined(SHx) || defined(_SHX_)
	#define POCO_ARCH POCO_ARCH_SH
	#if defined(__LITTLE_ENDIAN__) || (POCO_OS == POCO_OS_WINDOWS_CE)
		#define POCO_ARCH_LITTLE_ENDIAN 1
	#else
		#define POCO_ARCH_BIG_ENDIAN 1
	#endif
#elif defined (nios2) || defined(__nios2) || defined(__nios2__)
	#define POCO_ARCH POCO_ARCH_NIOS2
	#if defined(__nios2_little_endian) || defined(nios2_little_endian) || defined(__nios2_little_endian__)
		#define POCO_ARCH_LITTLE_ENDIAN 1
	#else
		#define POCO_ARCH_BIG_ENDIAN 1
	#endif
#elif defined(__AARCH64EL__)
	#define POCO_ARCH POCO_ARCH_AARCH64
	#define POCO_ARCH_LITTLE_ENDIAN 1
#elif defined(__AARCH64EB__)
	#define POCO_ARCH POCO_ARCH_AARCH64
	#define POCO_ARCH_BIG_ENDIAN 1
#endif

如何将本地字节序转成网络字节序呢?
本文最开始我提到,如果我们写代码时,不用考虑当前是大端还是小端,就方便极了。
poco同样提供了封装:fromNetwork和toNetwork,可以在ByteOrder.h中找到。如果你的项目中没有使用poco,又不想因为大小端问题引入一个库,那问题也不大,无非就是根据大小端情况封装fromNetwork和toNetwork而已,具体实现就直接参考poco的实现就行了。

参考文档:

https://github.com/pocoproject/poco/blob/develop/Foundation/include/Poco/Platform.h#L137
https://github.com/pocoproject/poco/blob/develop/Foundation/include/Poco/ByteOrder.h

原文链接:http://servercoder.com/2018/01/03/byte-order/