不同架构的字节顺序释疑.docx

上传人:b****1 文档编号:509478 上传时间:2023-04-29 格式:DOCX 页数:25 大小:42.23KB
下载 相关 举报
不同架构的字节顺序释疑.docx_第1页
第1页 / 共25页
不同架构的字节顺序释疑.docx_第2页
第2页 / 共25页
不同架构的字节顺序释疑.docx_第3页
第3页 / 共25页
不同架构的字节顺序释疑.docx_第4页
第4页 / 共25页
不同架构的字节顺序释疑.docx_第5页
第5页 / 共25页
不同架构的字节顺序释疑.docx_第6页
第6页 / 共25页
不同架构的字节顺序释疑.docx_第7页
第7页 / 共25页
不同架构的字节顺序释疑.docx_第8页
第8页 / 共25页
不同架构的字节顺序释疑.docx_第9页
第9页 / 共25页
不同架构的字节顺序释疑.docx_第10页
第10页 / 共25页
不同架构的字节顺序释疑.docx_第11页
第11页 / 共25页
不同架构的字节顺序释疑.docx_第12页
第12页 / 共25页
不同架构的字节顺序释疑.docx_第13页
第13页 / 共25页
不同架构的字节顺序释疑.docx_第14页
第14页 / 共25页
不同架构的字节顺序释疑.docx_第15页
第15页 / 共25页
不同架构的字节顺序释疑.docx_第16页
第16页 / 共25页
不同架构的字节顺序释疑.docx_第17页
第17页 / 共25页
不同架构的字节顺序释疑.docx_第18页
第18页 / 共25页
不同架构的字节顺序释疑.docx_第19页
第19页 / 共25页
不同架构的字节顺序释疑.docx_第20页
第20页 / 共25页
亲,该文档总共25页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

不同架构的字节顺序释疑.docx

《不同架构的字节顺序释疑.docx》由会员分享,可在线阅读,更多相关《不同架构的字节顺序释疑.docx(25页珍藏版)》请在冰点文库上搜索。

不同架构的字节顺序释疑.docx

不同架构的字节顺序释疑

交换字节

在计算领域存在两种不同的字节顺序处理方法(或者说是endian格式)。

endian格式指定了如何在内存中存储多字节数值中的各个字节;Big-endian的字节顺序处理方式表示存储多字节数据的时候权重最大的字节放在前面。

Little-endian的字节顺序处理方式则表示存储多字节数据的时候权重最小的字节放在前面。

PowerPC处理器使用big-endian的字节顺序处理方式,x86处理器家族则使用little-endian的字节顺序处理方式。

根据约定,多字节的数据在网络上发送的时候,使用big-endian的字节处理方式。

如果您的应用程序假定数据是某种endian格式,而实际上数据使用的是另一种格式,则程序就会不正确地解析数据。

您需要对代码中负责从磁盘或者网络读取多字节的数据(16位,32位,或者64位),或者将多字节数据写入到磁盘或网络的例程进行分析,因为这些例程对于字节顺序格式相当敏感。

有两个常见的处理字节顺序差别的方法:

在必要的时候进行字节交换,或者使用XML或其它与字节顺序无关的数据格式,比如CoreFoundation框架中的格式(CFPreferences,CFPropertyList,CFXMLParser)。

采用字节交换的方法,还是使用与字节顺序无关的数据格式存储数据,取决于您在应用程序中如何使用数据。

如果您需要支持某个现有的文件格式,则二进制兼容的解决方案是首先接受应用程序中已经在使用的big-endian文件格式,然后书写字节交换代码,在x86系统上读写文件时使用。

如果不需要支持老的文件,则可以考虑重新设计文件格式,以使用XML(extendedmarkuplanguage,即扩展的标志语言),XDR(externaldatarepresentation,即外部数据表示),或者NSCoding(ObjectiveC)来表示数据。

本章接下来的部分将描述为什么字节顺序会带来问题,给出交换字节的指导原则,描述MacOSX中提供的字节交换API,并且对大多数与字节顺序有关的情况提供解决方案。

本部分包括如下主要内容:

为什么字节顺序会带来问题

交换字节的指导原则

字节交换例程

字节交换策略

为字节交换数据书写和安装一个回调函数

相关信息

为什么字节顺序会带来问题

这个部分的例子的设计目的是更为详尽地向您展示字节顺序为什么会带来问题。

请看一下列表3-1中定义的C语言的数据结构。

它包含一个四个字节的整形数,一个字符串,以及一个双字节的整形数。

列表中的代码还初始化了这个结构。

列表3-1:

  包含多字节和单字节的数据结构

typedefstruct{

uint32_tmyOptions;

charmyStringArray[7];

shortmyVariable;

}myDataStructure;

 

myDataStructureaStruct;

 

aStruct.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系统的的字节组织顺序对比

在您查看图3-1时,请注意下面几点:

▪多字节数据,比如图中显示的32位和6位长的变量,在big-endian和little-endian系统中存储方式是不一样的。

在图中您可以看到,big-endian系统在存储数据时将权重最大的字节存放在最低的内存地址上,Little-endian系统在存储数据时则将权重最大的字节存放在最高的内存地址上。

因此,在big-endian系统上,myOptions变量(0xce)权重最小的字节被存放在地址为0x00000003的内存单元上,而在little-endian系统上则存放在地址为0x00000000的内存单元上。

▪单字节数据,比如myStringArray字符数组中的char值,在两种系统中都存在同一个内存位置上,无论其字节顺序格式是什么。

▪每个系统都进行字节填充,以保持4字节数据是对齐的。

在图中,填充字节用包含星号(*)的带阴影的方框来表示。

如果您希望在使用某个架构的系统上读取数据,而该数据是在使用不同架构的系统中写入的,并且您需要按字节访问数据,则多字节数据在内存中的字节顺序就会带来影响。

举例来说,如果您的应用程序需要访问myOptions变量的第二个字节,则当您从采用反向字节顺序的系统中读取数据的时候,结果就会得到myOptions变量的第一个字节,而不是第二个字节。

假定由列表3-1的代码初始化过的实例数据是在一个little-endian系统中生成,并存储到磁盘上,而且该数据是按照字节—地址的顺序写入磁盘的,数据在内存中的排列状态就如图3-1所示。

问题在于,即使在一个big-endian系统中解析这些数据进行解析,其字节顺序仍然是little-endian格式的。

这个差别会导致求值不正确。

在这个例子中,myOptions变量的值应该是0xfeedface,但是因为不正确的字节排列顺序,这个变量的求值结果为0xcefaedfe。

请注意:

big-endian和little-endian这两个术语来自JonathanSwift在十八世纪的嘲讽作品Gulliver’sTravels。

Blefuscu帝国的国民被根据吃鸡蛋的方式划分为两个部分:

一部分在吃鸡蛋的时候从鸡蛋的大端(bigend)开始,而另一部分则从鸡蛋的小端(littleend)开始。

交换字节的指导原则

下面的指导原则和本章梢后提供的策略可以帮助您确定程序中的字节交换代码是否合适。

▪在内存中,以本地的字节顺序保存数据。

只在从磁盘读取数据或者将数据写入磁盘的时候进行字节交换。

▪在可能的情况下,让编译器为您实现这些工作。

举例来说,当您使用诸如CoreFoundation中的CFSwapInt16BigToHost这样的函数时,编译器会确定该函数是否为您的目标处理器做过处理。

如果该代码什么都没有做,则不会被调用。

让编译器实现这个工作,比您自行使用#ifdef语句进行控制要更加有效。

▪如果您必须访问一个大的文件,则请考虑将数据以某种方式进行排列,以减少您必须进行的字节交换操作。

举例来说,您可以将最经常被访问的数据连续地排列在文件中。

这样,您只需要读取一块数据,并对之进行字节交换,而不用操作整个数据文件。

▪只有在必须的时候,才使用__BIG_ENDIAN__和__LITTLE_ENDIAN__宏。

不要使用检查特定处理器类型的宏,比如__i386__和__ppc__。

▪选择一个持续的字节顺序方法,并坚持使用这个方法。

也就是说,如果您按照一定的规则从磁盘读取数据或者向磁盘写入数据,请选择您希望使用的endian格式。

这样可以减少检查数据字节顺序的必要,从而减少可能的字节顺序交换。

▪记住哪些函数返回big-endian的数据,并正确处理该数据。

这类函数包括DNSServiceDiscovery函数(端口是网络字节顺序),以及ColorSync描述函数(所有数据都是big-endian)。

IconFamilyElement和IconFamilyResource数据类型(也包括IconFamilyPtr和IconFamilyHandle类型)总是big-endian格式的。

可能有一些其它的函数和数据类型没有列举在这里,请从相应的API参考资料中查找函数的返回数据信息。

▪请记住,字节交换会带来性能上的开销,因此只在有必要的时候交换字节。

字节交换例程

下面列举了提供字节交换例程的API。

绝大多数情况下,最好使用与您的编程框架相匹配的例程。

在下面列举的API中,CoreFoundation和FoundationAPI提供了浮点数值字节交换例程,其它API则没有。

▪POSIX(PortableOperatingSystemInterface,即可移植的操作系统接口)中的字节顺序处理函数(ntohl,htonl,ntohs,和htons)在man页面中有所描述,可以在Terminal或者Xcode中查看。

▪Darwin字节顺序处理函数和宏在头文件中定义。

虽然这个头文件是在libkern中定义的,但是在高级别的应用程序中使用也是可以接受的。

▪CoreFoundation的字节顺序处理函数在头文件中定义,其描述信息位于字节顺序工具参考部分。

如果需要如何使用这些函数的详细信息,请参见内存管理一文中的字节交换部分。

▪Foundation的字节顺序处理函数在头文件中定义,其描述信息位于ObjectiveC的Foundation参考部分。

▪CoreEndianAPI在头文件中定义。

这个头文件中的字节交换函数在QuickTime参考文档中进行描述。

这个API中的函数名称都以Endian作为前缀。

您可以通过QuickTime参考中的函数的字母索引部分找到这些函数的描述信息。

请注意:

在您使用字节交换例程的时候,编译器会对您的代码进行优化,以使这些例程只在代码运行所在的架构需要的时候才被执行。

字节交换策略

交换字节的策略取决于数据的格式,不存在可以处理所有字节顺序差别的通用例程。

单字节的字符串完全不需要交换,32位的数量型需要对四个字节进行颠倒交换,16位的数量型则需要在两个字节之间进行颠倒交换。

任何需要交换数据的程序都必须知道相应数据的类型,源数据的endian顺序,以及当前宿主系统的endian顺序。

本部分针对下面这些数据类型列出了各种字节交换的策略,内容上按照字母顺序进行组织:

▪“常数”

▪“定制的苹果事件数据”

▪“定制的资源数据”

▪“浮点数”

▪“整型数”

▪“网络相关的数据”

▪“OSType-to-String的转换”

▪“Unicode文本文件”

常数

常数作为编译完成的执行文件的一部分,其字节顺序是宿主系统的字节顺序。

只有当常数成了不在本地维护的数据的一部分,或者需要在不同的宿主系统之间进行转移,才需要进行字节交换。

在大多数的情况下,您可以在前期进行字节交换,或者通过移位或其它简单的操作符,让预处理器进行必要的数学运算。

如果您定义和使用的结构必须以某种特定的endian格式存在于内存中,则可以使用libkern/OSByteOrder.h头文件定义的OSSwapConst宏和OSSwap*Const变量来进行处理。

这些宏可以在高级别的应用程序中使用。

定制的苹果事件数据

苹果事件(AppleEvent)是一种遵循苹果事件进程间消息传递协议(AppleEventInterprocessMessagingProtocol)的高级事件。

苹果事件管理器(AppleEventManager)负责在同一个电脑的应用程序,或者在远程电脑上的应用程序之间发送苹果事件。

您也可以定义自己的苹果事件数据类型,然后通过苹果事件管理器的API发送和接收苹果事件。

MacOSX会为您管理系统定义的苹果事件数据类型,并为当前正在执行的代码正确地处理这些类型。

您不需要执行任何特殊的任务。

当您的应用程序从苹果事件抽取系统定义的数据时,系统会在向您的应用程序发送事件之前,将数据进行字节交换。

您需要按照本地的endian格式来解析苹果事件中的系统定义数据类型。

类似地,如果您将一个本地endian格式的数据放在一个即将发送的苹果事件中,而且该数据是系统定义的数据类型,则接收方也能够以其本地endian格式解析这个数据。

然而,对于您自行定义的定制苹果事件,必须考虑字节顺序的差异。

通过下面的任何一种方法都可以完成这个工作:

▪书写字节交换回调函数(也称为flipper),并提供给系统。

每当系统确定您的苹果事件需要进行字节交换的时候,就激活您的flipper程序,以确保数据的接收方能够收到正确endian格式的数据。

如果需要细节信息,请参见“书写和安装字节交换数据的回调函数”。

▪不管具体的架构是什么,选用一种的endian格式。

这样,在读写定制的苹果事件数据的时候,就可以使用big-to-host和host-to-big例程,比如CoreFoundation的字节顺序工具(ByteOrderUtilities)中的CFSwapInt16BigToHost和CFSwapInt16HostToBig函数。

定制的资源数据

在MacOSX上推荐的提供资源的方法是在应用程序包中提供一些文件,来定义诸如图像文件,声音,本地化文本,以及归档的用户界面这样的资源。

而本文的这个部分讨论的资源数据是指Carbon支持的资源管理器风格(ResourceManager-style)的资源文件中定义的资源。

资源管理器在MacOSX之前就已经推出了。

如果您的应用程序使用了资源管理器风格的资源文件,则应该考虑将它们向应用程序包中的MacOSX风格的资源转换。

资源通常包含描述菜单,窗口,控件,对话框,声音,字体,和图标数据。

系统定义了很多标准的资源类型(比如'movv',用于指定一个QuickTime电影,还有'MENU',用于定义菜单),您可以创建自己的私有资源类型,在自己的应用程序中使用。

通过资源管理器的API,您可以定义资源数据类型,以及获取和设置资源数据。

MacOSX在内存中跟踪资源,使您的应用程序可以读写资源。

应用程序和系统软件根据资源的类型为资源解析数据。

虽然在通常情况下,您会让操作系统自行读取资源(比如应用程序图标),但是您也可以直接调用资源管理器函数来读写资源。

MacOSX为您管理系统定义的资源,并为当前正在运行的代码正确地处理这些资源。

也就是说,如果您的应用程序运行在一个x86系统上,则MacOSX会进行相应的字节交换,使应用程序的图标,菜单,以及其它标准资源正确显示。

您不需要任何特殊的工作。

但是如果您为自己的应用程序定义了私有的资源,则从磁盘读或者向磁盘写资源的时候需要考虑不同架构之间的字节顺序差异。

您可以使用下面的策略来处理资源管理器风格的定制资源数据。

请注意,这些策略和用于处理定制苹果事件数据的策略是相同的:

▪为系统提供一个字节交换回调函数。

每当系统确定您的资源数据必须进行字节交换的时候,就调用该函数。

如果需要细节信息,请参见“书写和安装字节交换数据的回调函数”。

▪不管具体架构是什么,总是使用同样的endian格式来写数据。

这样,在读写您自己定制的资源数据的时候,就可以使用big-to-host和host-to-big例程,比如CoreFoundation的字节顺序工具(ByteOrderUtilities)中的CFSwapInt16BigToHost和CFSwapInt16HostToBig函数。

请注意:

如果您正在修改采用预装载位(preloadbit)标识资源的代码,则应该将预装载位从所有需要经过字节交换处理的资源中删除。

在MacOSX中,预装载位几乎总是没有必要的。

如果不能删除预装载位,则应该在资源读取完成之后才对资源数据进行字节交换。

您不能用flipper回调函数来自动进行字节交换,因为在MacOSX上,预装载位会导致资源在运行任何应用程序代码之前,首先被读取。

浮点数

CoreFoundation定义了一套函数以及两个特殊的数据类型来帮助您处理浮点数。

这些函数使您可以用某种方式对32位和64位的浮点数进行编码,从而使您可以在必要的时候进行解码和字节交换。

列表3-2向您说明如何对64位的浮点数进行编码,而列表3-3说明如何解码。

列表3-2:

  对一个浮点数进行编码

Float64myFloat64;

CFSwappedFloat64swappedFloat;

//Encodethefloating-pointvalue.

swappedFloat=CFConvertFloat64HostToSwapped(myFloat64);

CFSwappedFloat32和CFSwappedFloat64数据类型将浮点数包含在一个规范表示中。

CFSwappedFloat数据类型本身不是一个浮点数,不应该直接被使用。

然而您可以将它发送给另一个进程,将它存储在磁盘,或者通过网络进行发送。

由于该格式可以通过转换函数和规范格式进行相互转换,因此不需要显式地进行处理字节交换问题。

如果需要的话,在格式转换的过程中就会自动处理这个问题。

列表3-3:

  对一个浮点数进行解码

Float64myFloat64;

CFSwappedFloat64swappedFloat;

//Decodethefloating-pointvalue.

myFloat64=CFConvertFloat64SwappedToHost(swappedFloat);

NSByteOrder.h头文件中也定义了一些函数,与这里讨论的CoreFoundation定义的函数相类似。

整型数

系统库中的字节访问函数,比如OSReadLittleInt16和OSWriteLittleInt16,提供了基本的字节交换功能。

如果本地的endian格式和目标的endian格式不同,这些函数就进行字节交换。

它们在libkern/OSByteOrder.h头文件中定义。

请注意:

OSReadXXX和OSWriteXXX类型函数要比OSSwapXXX类型的函数或者其它较为高级的框架中的函数性能好。

CoreFoundation为字节交换提供了三个优化过的基本函数—CFSwapInt16,CFSwapInt32,和CFSwapInt64。

所有其它的字节交换函数都使用这三个基本函数来完成它们的工作。

一般地说,您不必直接使用这些基本函数。

基本的字节交换函数是无条件地进行交换,更高级别的交换函数则不同,它们以下面的方式来进行工作:

当字节不需要交换的时候—换句话说,当源和宿主系统的字节顺序相同的时候,函数什么都不做。

对于整形数类型,这些函数的形式是CFSwapXXXBigToHost和CFSwapXXXLittleToHost,CFSwapXXXHostToBig,和CFSwapXXXHostToLittle,其中XXX是数据类型,比如Int32。

举例来说,在一个little-endian的机器上,您可以用CFSwapInt16BigToHost函数来从网络上读取一个16位的整数,网络上数据的字节顺序是网络字节顺序(big-endian)。

列表3-4演示这个过程。

列表3-4:

  将一个16位的整数从big-endian转换为主机系统的endian格式

SInt16bigEndian16;

SInt16swapped16;

//Swapa16-bitvaluereadfromthenetwork.

swapped16=CFSwapInt16BigToHost(bigEndian16);

假定整形数是一个数据结构中的成员,列表3-5中演示如何完成相应的字节交换。

列表3-5:

  将一个整数从little-endian转换为主机系统的endian格式

//Byteswapthevaluesifnecessary.

aStruct.int1=CFSwapInt32LittleToHost(aStruct.int1)

aStruct.int2=CFSwapInt32LittleToHost(aStruct.int2)

字节交换代码只有在必要的时候才交换字节。

如果宿主系统是big-endian架构,则例子代码中使用的函数就会对每个成员进行字节交换;而当代码运行在little-endian的机器上时,字节交换代码则什么都不做—编译器对代码进行了优化。

网络相关的数据

与网络相关的数据(IP地址,端口号,等等)通常使用big-endian格式(也称为网络字节顺序),因此在网络和x86系统之间进行通讯的时候,您可能需要进行字节交换。

在向网络传递数据,或者从网络接收数据的时候,您可能永远都不需要调整PowerPC代码。

在x86系统上,您则必须仔细查看网络通讯代码,确保自己总是以正确的字节顺序发送与网络有关的数据。

您还必须正确处理从网络上接收的数据,将数值通过字节交换处理为适合于宿主系统的微处理器的endian格式。

您可以使用下面这些POSIX函数来在网络字节顺序和宿主系统的字节顺序之间进行转换(其它字节交换函数,比如OSByteOrder.h和CFByteOrder.h头文件中定义的那些函数,也可以用于处理网络数据)。

▪网络到宿主系统:

uint32_tntohl(uint32_tnetlong);

uint16_tntohs(uint16_tnetshort);

▪宿主系统到网络:

uint32_thtonl(uint32_thostlong);

uint16_thtons(uint16_thostshort);

这些函数的信息技术在man页面中,可以在Terminal或者Xcode查看。

sockaddr_in结构中的sin_saddr.s_addr和sin_port成员的字节顺序应该总是网络字节顺序。

通过阅读man页面文档,您可以找出适合于BSD网络通讯函数的每个参数的endian格式。

OSType-to-String的转换

您可以使用UTCreateStringForOSType和UTGetOSTypeFromString函数来在类型为OSType的值和CFString对象(CFStringRef数据类型)之间相互转换。

这些函数在统一类型标识符概述一文中讨论,它们的定义位于UTType.h头文件中,该头文件是LaunchServices框架的一部分。

当您使用四字符的标识符时,请记住“abcd”!

='abcd',而应该是'abcd'==0x61626364。

您必须将'abcd’当作一个整形数,而不是字符串数据,因为'abcd'是一个32位整数的便利方式(一个FourCharCode数据类型就是一个UInt32数据类型)。

编译器并不自动进行字节交换。

如果您需要处理每个单独的字符,则可以使用移位操作符。

举例来说,如果您现在用标准的Cprintf风格的语法打印一个OSType或者FourCharCode类型的值,则请用下面的方式:

printf("%c%c%c%c",(char)(val>>24),(char)(val>>16),

(char)(val>>8),(char)val)

而不要用下面这种方式:

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 经管营销 > 经济市场

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2