这个标题中的Endian是什么意思呢?还是让我们先来看看下面的情况,这是内存中一个WORD值中的内容,那么这个WORD中的值是0x1234呢,还是0x3412 ?
low byte high byte
0x12 0x34
熟悉x86汇编的人立刻就知道这个值应为0x3412,很对,但在一些情况下,比如说你在SGI的机器上看到这种情况,则正好相反,0x1234才是正确答案,这与CPU内部处理数据的方式有关。这两种处理方式都存在于不同厂商生产的CPU之中,在上例中若此WORD值为0x3412的,我们称之为little-endian, 若为0x1234的,我们称之为big-endian,这是两种不同的byte orders。MSDN中有比较精确的定义如下:
Byte Ordering Byte ordering Meaning
big-endian The most significant byte is on the left end of a word.
little-endian The most significant byte is on the right end of a word.
一般来说我们不用关心byte ordering的问题,但若要涉及跨平台之间的通信和资源共享,则不得不考虑这个问题了。也许你会说,我永远不会去用其它非x86的CPU,也许是这样,你甚至可以不必知道我们最常用的Intel,AMD等生产的x86的byte ordering是little-endian的,而且按现在的装机数量来看,可以说世界上绝大多数CPU是little-endian的,但多了解一些没有什么坏处,也许有用上的一天,实际若您要涉及到网络编程,了解一些还是有所帮助的,看完本文后您就应该知道为何socket编程中为何要用到如 ntohl, htonl, ntohs, htons这几个看起来名字似乎怪怪的API了,也很容易理解这些函数名的意义了。
假设我们要在不同byte ordering的机器之间传输和交换数据,那该怎么办呢,有两个方法,一是全部转换成文本来传输(如XML使用的),另一个方法两方都按照某一方的byte order,这时就涉及到了不同byte order之间相互转换的问题(网络传输标准如TCP/IP采用第二种方法并且由于历史的原因,byte ordering是big-endian的)。两种之间该如何转换呢?方法有很多,我们可以先看看MFC中在处理serialize的代码中所用的方法(List), 虽然代码应该是高效易读的, 但我个人并不喜欢它, 原因是我觉得这不是一种通用优美的方法.下面列出的是我自己写的转换的代码:
template
F3D_INLINE T ConvertEndian(T t)
{
T tResult = 0;
for (int I = 0; I < sizeof(T); ++ I)
{
tResult <<= 8;
tResult |= (t & 0xFF) ;
t >>= 8;
}
return tResult;
}
原理非常简单,交换字节顺序,我就不多说了,当然这个写法并不是快速的, 只是通用的(我没条件试, 若有不对之处请指出), 若要快速的代码,可以在不同platform上用与platform相关的代码, 如在PowerPC上有 "load word byte-reversed indexed" (lwbrx) 和 "load halfword byte-reversed indexed" (lhbrx) 指令, 在x86上还可用BSWAP单个汇编指令等,在类型上专为int16, int32写的通用的代码也可以比这快得多.
当然如果在byte ordering相同的情况下,应该不必用这个转换函数,所以我们可以定义一个宏来处理不同的byte ordering,也可以在运行时测试byte ordering, 下面的代码给出了一个简单的测试方法。
// Test for endianness.
F3D_INLINE bool IsLittleEndian(void)
{
DWORD dwTestValue = 0x12345678L;
return (*((BYTE*)&dwTestValue) == 0x78);
}
但是float比较怪,有可能所涉及到不仅仅是byte order的问题,因为有些平台如Alpha不使用IEEE的浮点格式,还得自己转换。当然同上,其它的方法一是将所用的float用文本方式输入输出,另一个办法是在某些情况下可将其转换成定点数再处理,这里我不再深入。
如果是读写第三方已经指定byte order的文件或数据流,比如说读SGI的位图文件格式,则可以直接自行按指定的byte order拼起来,不必考虑host机是何种byte ordering。下面我给出相应的代码:
// Read a little-endian TYPE from address
template
F3D_INLINE T GetLittleEndian(const BYTE* pBuf)
{
T tResult = 0;
pBuf += sizeof(T) - 1;
for (int I = 0; I < sizeof(T); ++ I)
{
tResult <<= 8;
tResult |= *pBuf --;
}
return tResult;
}
// Read a big-endian TYPE from address
template
F3D_INLINE T GetBigEndian(const BYTE* pBuf)
{
T tResult = 0;
for (int I = 0; I < sizeof(T); ++ I)
{
tResult <<= 8;
tResult |= *pBuf ++;
}
return tResult;
}
// Set a little-endian TYPE on a address
template
F3D_INLINE void SetLittleEndian(BYTE* pBuf, T t)
{
for (int I = 0; I < sizeof(T); ++ I)
{
*pBuf ++ = BYTE(t & 0xFF);
t >>= 8;
}
}
// Set a big-endian T on a address
template
F3D_INLINE void SetBigEndian(BYTE* pBuf, T t)
{
pBuf += sizeof(T) - 1;
for (int I = 0; I < sizeof(T); ++ I)
{
*pBuf -- = BYTE(t & 0xFF);
t >>= 8;
}
}
从上文可以看出,byte order挺简单的,一般应用中可能也用不上,但若您对写跨平台的程序有兴趣,则一定要了解的比较清楚才行。以上代码都是从实际使用的源码中取下来的。
附:常见Processor, OS的byte ordering情况
Processor OS Order
x86 (Intel, AMD, … ) All little-endian
DEC Alpha All little-endian
HP-PA NT little-endian
HP-PA UNIX big-endian
SUN SPARC All? big-endian
MIPS NT little-endian
MIPS UNIX big-endian
PowerPC NT little-endian
PowerPC non-NT big-endian
RS/6000 UNIX big-endian
Motorola m68k All big-endian