《不同架构的字节顺序释疑.doc》由会员分享,可在线阅读,更多相关《不同架构的字节顺序释疑.doc(16页珍藏版)》请在金锄头文库上搜索。
1、交换字节在计算领域存在两种不同的字节顺序处理方法(或者说是endian格式)。endian格式指定了如何在内存中存储多字节数值中的各个字节;Big-endian的字节顺序处理方式表示存储多字节数据的时候权重最大的字节放在前面。Little-endian的字节顺序处理方式则表示存储多字节数据的时候权重最小的字节放在前面。PowerPC处理器使用big-endian的字节顺序处理方式,x86 处理器家族则使用little-endian的字节顺序处理方式。根据约定,多字节的数据在网络上发送的时候,使用big-endian的字节处理方式。如果您的应用程序假定数据是某种endian格式,而实际上数据使用
2、的是另一种格式,则程序就会不正确地解析数据。您需要对代码中负责从磁盘或者网络读取多字节的数据(16位,32位,或者64位),或者将多字节数据写入到磁盘或网络的例程进行分析,因为这些例程对于字节顺序格式相当敏感。有两个常见的处理字节顺序差别的方法:在必要的时候进行字节交换,或者使用XML或其它与字节顺序无关的数据格式,比如Core Foundation框架中的格式(CFPreferences,CFPropertyList,CFXMLParser)。采用字节交换的方法,还是使用与字节顺序无关的数据格式存储数据,取决于您在应用程序中如何使用数据。如果您需要支持某个现有的文件格式,则二进制兼容的解决方
3、案是首先接受应用程序中已经在使用的big-endian文件格式,然后书写字节交换代码,在x86系统上读写文件时使用。如果不需要支持老的文件,则可以考虑重新设计文件格式,以使用XML(extended markup language,即扩展的标志语言),XDR(external data representation,即外部数据表示),或者NSCoding(Objective C)来表示数据。本章接下来的部分将描述为什么字节顺序会带来问题,给出交换字节的指导原则,描述Mac OS X中提供的字节交换API,并且对大多数与字节顺序有关的情况提供解决方案。本部分包括如下主要内容:为什么字节顺序会带来
4、问题交换字节的指导原则字节交换例程字节交换策略为字节交换数据书写和安装一个回调函数相关信息为什么字节顺序会带来问题这个部分的例子的设计目的是更为详尽地向您展示字节顺序为什么会带来问题。请看一下列表3-1中定义的C语言的数据结构。它包含一个四个字节的整形数,一个字符串,以及一个双字节的整形数。列表中的代码还初始化了这个结构。列表3-1 :包含多字节和单字节的数据结构typedef struct uint32_t myOptions; char myStringArray 7; short myVariable; myDataStructure;myDataStructure aStruct; a
5、Struct.myOptions = 0xfeedface; strcpy(aStruct.myStringArray, safari); aStruct.myVariable = 0x1234;请对比一下(参见图 3-1)在big-endian和little-endian系统上是如何将这个结构存储在内存中的。在big-endian系统中,每个数据字节在内存中的地址是随着各个字节的权重从大到小递增;而在little-endian系统中,每个数据字节在内存中的地址是随着各个字节的权重从小到大递增。图 3-1 :Big-endian系统和little-endian系统的的字节组织顺序对比在您查看图
6、3-1时,请注意下面几点: 多字节数据,比如图中显示的32位和6位长的变量,在big-endian和little-endian系统中存储方式是不一样的。在图中您可以看到,big-endian 系统在存储数据时将权重最大的字节存放在最低的内存地址上,Little-endian系统在存储数据时则将权重最大的字节存放在最高的内存地址上。因此,在big-endian系统上,myOptions变量(0xce)权重最小的字节被存放在地址为0x00000003的内存单元上,而在little-endian 系统上则存放在地址为0x00000000的内存单元上。 单字节数据,比如myStringArray字符数
7、组中的char值,在两种系统中都存在同一个内存位置上,无论其字节顺序格式是什么。 每个系统都进行字节填充,以保持4字节数据是对齐的。在图中,填充字节用包含星号(*)的带阴影的方框来表示。如果您希望在使用某个架构的系统上读取数据,而该数据是在使用不同架构的系统中写入的,并且您需要按字节访问数据,则多字节数据在内存中的字节顺序就会带来影响。举例来说,如果您的应用程序需要访问myOptions变量的第二个字节,则当您从采用反向字节顺序的系统中读取数据的时候,结果就会得到myOptions变量的第一个字节,而不是第二个字节。假定由列表 3-1的代码初始化过的实例数据是在一个little-endian系
8、统中生成,并存储到磁盘上,而且该数据是按照字节地址的顺序写入磁盘的,数据在内存中的排列状态就如图 3-1所示。问题在于,即使在一个big-endian系统中解析这些数据进行解析,其字节顺序仍然是little-endian格式的。这个差别会导致求值不正确。在这个例子中,myOptions变量的值应该是0xfeedface,但是因为不正确的字节排列顺序,这个变量的求值结果为0xcefaedfe。请注意:big-endian和little-endian这两个术语来自Jonathan Swift在十八世纪的嘲讽作品Gullivers Travels。 Blefuscu帝国的国民被根据吃鸡蛋的方式划分为
9、两个部分:一部分在吃鸡蛋的时候从鸡蛋的大端(big end)开始,而另一部分则从鸡蛋的小端(little end)开始。交换字节的指导原则下面的指导原则和本章梢后提供的策略可以帮助您确定程序中的字节交换代码是否合适。 在内存中,以本地的字节顺序保存数据。只在从磁盘读取数据或者将数据写入磁盘的时候进行字节交换。 在可能的情况下,让编译器为您实现这些工作。举例来说,当您使用诸如Core Foundation中的CFSwapInt16BigToHost这样的函数时,编译器会确定该函数是否为您的目标处理器做过处理。如果该代码什么都没有做,则不会被调用。让编译器实现这个工作,比您自行使用#ifdef语句
10、进行控制要更加有效。 如果您必须访问一个大的文件,则请考虑将数据以某种方式进行排列,以减少您必须进行的字节交换操作。举例来说,您可以将最经常被访问的数据连续地排列在文件中。这样,您只需要读取一块数据,并对之进行字节交换,而不用操作整个数据文件。 只有在必须的时候,才使用_BIG_ENDIAN_和_LITTLE_ENDIAN_宏。不要使用检查特定处理器类型的宏,比如_i386_和_ppc_。 选择一个持续的字节顺序方法,并坚持使用这个方法。也就是说,如果您按照一定的规则从磁盘读取数据或者向磁盘写入数据,请选择您希望使用的endian格式。这样可以减少检查数据字节顺序的必要,从而减少可能的字节顺序
11、交换。 记住哪些函数返回big-endian的数据,并正确处理该数据。这类函数包括DNSServiceDiscovery函数(端口是网络字节顺序),以及ColorSync描述函数(所有数据都是big-endian)。 IconFamilyElement 和IconFamilyResource 数据类型(也包括IconFamilyPtr和IconFamilyHandle类型)总是big-endian格式的。可能有一些其它的函数和数据类型没有列举在这里,请从相应的API参考资料中查找函数的返回数据信息。 请记住,字节交换会带来性能上的开销,因此只在有必要的时候交换字节。字节交换例程下面列举了提供字
12、节交换例程的API。绝大多数情况下,最好使用与您的编程框架相匹配的例程。在下面列举的API中,Core Foundation和Foundation API提供了浮点数值字节交换例程,其它API则没有。 POSIX (Portable Operating System Interface,即可移植的操作系统接口)中的字节顺序处理函数(ntohl,htonl,ntohs,和htons)在man页面中有所描述,可以在Terminal或者Xcode中查看。 Darwin字节顺序处理函数和宏在头文件中定义。虽然这个头文件是在libkern中定义的,但是在高级别的应用程序中使用也是可以接受的。 Core
13、Foundation的字节顺序处理函数在头文件中定义,其描述信息位于字节顺序工具参考部分。如果需要如何使用这些函数的详细信息,请参见内存管理一文中的字节交换部分。 Foundation的字节顺序处理函数在头文件中定义,其描述信息位于Objective C的Foundation参考部分。 Core Endian API在头文件中定义。这个头文件中的字节交换函数在QuickTime参考文档中进行描述。这个API中的函数名称都以Endian作为前缀。您可以通过QuickTime参考中的函数的字母索引 部分找到这些函数的描述信息。请注意:在您使用字节交换例程的时候,编译器会对您的代码进行优化,以使这些
14、例程只在代码运行所在的架构需要的时候才被执行。字节交换策略交换字节的策略取决于数据的格式,不存在可以处理所有字节顺序差别的通用例程。单字节的字符串完全不需要交换,32位的数量型需要对四个字节进行颠倒交换,16位的数量型则需要在两个字节之间进行颠倒交换。任何需要交换数据的程序都必须知道相应数据的类型,源数据的endian顺序,以及当前宿主系统的endian顺序。本部分针对下面这些数据类型列出了各种字节交换的策略,内容上按照字母顺序进行组织: “常数” “定制的苹果事件数据” “定制的资源数据” “浮点数” “整型数” “网络相关的数据” “OSType-to-String的转换” “Unicod
15、e文本文件”常数常数作为编译完成的执行文件的一部分,其字节顺序是宿主系统的字节顺序。只有当常数成了不在本地维护的数据的一部分,或者需要在不同的宿主系统之间进行转移,才需要进行字节交换。在大多数的情况下,您可以在前期进行字节交换,或者通过移位或其它简单的操作符,让预处理器进行必要的数学运算。如果您定义和使用的结构必须以某种特定的endian格式存在于内存中,则可以使用libkern/OSByteOrder.h头文件定义的OSSwapConst宏和OSSwap*Const变量来进行处理。这些宏可以在高级别的应用程序中使用。定制的苹果事件数据苹果事件(Apple Event)是一种遵循苹果事件进程间消息传递协议(Apple Event Interprocess Messaging Protocol)的高级事件。苹果事件管理器(Apple Event Manager)负责在同一个电脑的应用程序,或者在远程电脑上的应用程序之间发送苹果事件。您也可以定义自己的苹果事件数据类型,然后通过苹果事件管理器的API发送和