转:编写跨平台的软件入门——有关字节对齐
2010-07-05 15:22
169 查看
标题:编写跨平台的软件入门——有关字节对齐
2008-03-18 09:40:29
2008-03-18 09:40:29
一, 为什么要跨平台? 你想过把你的 Windows 上编写的程序在 Linux 编译运行吗,以及在 Mac 或其他 OS 上运行等等?反过来也一样?这就需要涉及到跨平台编程知识。这里需要注意的是,平时很多在一个平台运行的程序在跨平台的时候变的不再正确。 Java 并非真的是跨平台的开发环境,它是运行在它自己的平台上。这里主要关注 C 和 C++ 的跨平台开发。 下面主要就几个方面来讨论跨平台编程的注意事项: 1. 字节序 2. 字节填充 3. 其他 二, 字节序 大家都知道计算机使用两种字节序,一种是 little-endian ,另一种是 big-endian 。这主要是由于当前流行的 CPU 之间的差异造成的,基本上是 IBM-PowerPC 使用的大序,而其他 CPU 使用的小序。 这里先来介绍一下 little-endian 和 big-endian 之间的具体差异。 X86 指令集合使用小序( little-endian )字节顺序;这就意味着多个字节值的最重要字节在地址的最低位。小序很早就使用,因为硬件容易实现,但和今天的制造商技术有点不同;但在第一代 IBM PC 机的 Vaxen 和 8086 处理器使用是它如此流行的主要原因。 看一个例子:
而另一方面 PowerPC 和 Sparc 芯片是 big-endian 的,也就是说,最重要的字节存储在较低的地址。对于 CPU 需要额外的电路实现这个功能,但对于今天的处理器技术与缓存控制技术相比较显的微不足道。使用 BIG-ENDIAN 的最大好处是在使用低级调式器时比较容易理解数据的存储,同样对于文件十六进制 DUMP 或网络 Sniffer 显示也是一样的。 对于 BIG-ENDIAN ,上面的例子中内存如下表示:
下面看几个关于字节序的问题: 1. Long 型指针和 char 指针之间的转换 看下面这段代码
在一个 little-endian 处理器上, charVal 是 0 ,而在一个 big-endian 处理器上, charVal 的值是 3 。这样的问题是最难以发现的问题之一。 为了避免这个错误,使用一个临时变量可以解决这个问题,如下:
2. 读写文件和写网络数据 在从文件读数据或写数据到文件的时候以及网络,对于字节顺序的处理一定要小心;一定记住不能将多个字节的数据写到文件或网络上;例如:
解决多字节的读写有很多办法,这里提供两种。 方法 1 : 写的代码
由于字节顺序的问题导致在处理的时候需要进行字节交换或类似 2 中方法 1 的处理,这里称为交换。通常情况下,做字节顺序的交换并不影响,因为交换两个字节或四个字节值只需要很少的 CPU 指令,并且完全可以在寄存器中执行。 但如果有很多数据需要交换,例如:一个 1024*768 位图的图像,在这么大的循环中执行是影响性能的。 另外对于 3 的运行时检查字节序的代码要查看具体的位置。如果仅仅调用一次或几次,不会影响性能,如果对于上面的这个循环中调用,对性能的影响是显著的,这个时候可以使用一个预编译宏来分别处理。例如:
另一个写可移植代码的注意点是结构体的字节对齐和填充。通常,在单个平台上,如果需要保存一个结构体到文件,那么是作为一个整体写到文件的,如下:
一种可能我们认为的情况是:
有些处理器不能从某些位置读或写多个字节;几乎所有的都不能从奇数地址来读数据。通常他们只读那些是 sizeof ( value )倍数的地址;对于四个字节只能读地址是 4 个字节的倍数,对于 2 个字节的 short 只能读两个字节倍数的地址。如果不遵从这个字节对齐的规律,处理器会抛出一个异常并且终止程序,有些系统上会锁定机器(如果发生在 kernel 中)。 有时,读没有对齐的数据需要花费额外的时间。例如: PowerPC 能够读任何偶数地址,但对于那些不能被 4 整除的地址需要耗费额外的总线周期。为了读一个 long 数值( value )在 2 整除而不是 4 整除的地址,它将读四个字节并包括需要读的值的上面两个字节,抛弃 2 个字节,然后读另外四个包含 value 低 2 个字节的字节,同样抛弃另外两个。这与读 4 个字节对齐的地址相比需要多访问一次缓存。 为了达到字节对齐的目的,编译器会插入未命名的填充字节到结构体中。至于插入几个字节是通过编译器和 OS 或库内存分配器一起决定的。 在 Windows VC 编译器中,可以使用 #pragma 来指定字节对齐的方式。 总而言之,在定义结构的时候要按照字节边界对齐来定义,一般按照 4 个字节,如果不够就需要增加填充字段。 另外对于结构体写文件或输出到网络上,最好的办法是按照成员来逐个写入或发送,这可以避免将垃圾数据存放到文件中或传输到网络上。 四, 其他 下面是几个笔者在实际编写代码中发生过的错误,这里与大家一道分析一下。 1. 示例 1 :
C++ 标准说:在 for 循环内部声明的变量在 for 结束的时候无效,因此可以连续使用再次在 for 循环中使用该记数器变量。但很不幸的是很多编译器都提供编译选项来让你觉得变量是否在 for 循环以后仍然有效。 VC 中默认编译选项 /Ze 用来指定 for 循环变量的局部性,但并非所有的编译器都是将这个选项作为默认编译参数;所以为了能让你的代码可以在任意平台编译通过,使用 C 风格的会有保证一点;如下:
Int 型变量是一个奇怪的东西,它在 16 位机器上是 2 个字节,在 32 位机上是 4 个字节;将来可能在 64 位机上是 8 个字节。所以如果你的代码中有对 int 的使用,而你想代码可以在很多平台上运行,那么一定要注意了。看一下下面的情况:
同样在使用 int 型变量写文件和输出到网络时都要小心这个问题。最好的办法是,在这些情况下不要使用 int 型变量; int 型变量仅仅在程序内部使用。 3. 关于 Bit field 的问题 在 C 语法中有 bit field 的语法,可以根据需要来定义一个符号具体占用的 bit 数,例如:
pTest->a = 6; pTest->b =2 ; pTest->c =2; 细心的读者可能发现这里的问题所在,原因在于不同的编译器对 bit field 进行了不同的处理。在 Windows 平台上, c 被放在字节的最高两位,而 a 被放在字节的最低 4 位,在 MAC 上正好相反。但一定要注意,这是编译器行为,而不是数据在传输过程中发生了字节的位交换。在 Windows 发送到网络的时候, buf[0] 的内容二进制表示为:
为了避免这个问题,请不要在写文件或网络输出的时候使用 BIT FILED 语法,如果一定要使用请注意编译器对位处理的区别。 n 五 小结 其实实际工作中,大家认为自己的代码都不需要在多个平台上运行,而认为跨平台编码与自己无关;其实不然,好的编码习惯是慢慢养成的,如果大家都知道这些跨平台编码的细节,在开始写代码的时候就开始避免这样的问题,一旦有一天我们的代码需要跨平台运行或一点我们要写跨平台代码时,我们就不会无从下手,而是顺其自然,因为我们已经具备了这样的习惯。 当然这里的介绍只是一个开始,跨平台编码涉及的问题还很多,由于笔者经验的限制不能一一描述。 |
相关文章推荐
- 编写跨平台的软件入门
- (转)编写跨平台的软件入门
- 编写跨平台的软件入门
- 编写跨平台的软件入门
- 软件文档编写入门——软件工程视频总结(二)
- 有关字节对齐的知识汇总
- 有关字节对齐的深入思考
- [软件渲染器入门]一,编写相机、网格和设备对象的核心逻辑
- 关于字节对齐,和程序优化有关 值得一看
- 有关软件测试计划编写注意事项
- 软件文档编写入门——软件工程视频总结(二)
- 软件文档编写入门——软件工程视频总结(一)
- 有关字节对齐的介绍
- 软件测试技术JUnit和单元测试入门简介--单元测试及软件测试技术概念以及JUnit编写原则和特征
- 初学入门:如何有效编写软件的75条建议
- [转]初学入门:如何有效编写软件的75条建议
- 跨平台的字节对齐实现
- 有关字节对齐的问题
- 有关字节对齐的介绍:
- 【转】组策略软件限制策略规则包编写之菜鸟入门(修正版)