跨平台项目中的字节序转换问题

跨平台项目开发中,经常会遇到一个古老的问题——字节序转换。如果一个文件是在小端字节序的机器上生成的,那么一个整数255可能被存储为:

ff 00 00 00

但是当它被读入内存时,在不同的平台上其值会有所不同。这将导致移植问题。

C++中的字节序转换

在C++中,可以通过编写一个名为readInt()的函数来解决这个问题。

void readInt(void *p, FILE* file) { char buf[4]; fread(buf, 4, 1, file); *((uint32*)p) = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; }

这个函数的优点是可以在大端和小端平台上工作。但它剥夺了读取结构体的便利。

使用宏进行字节序转换

可以保留代码不变,使用几个宏来后处理数据。

fread(&header, sizeof(struct MyFileHeader), 1, file); CQ_NTOHL(header.version); CQ_NTOHL_ARRAY(&header.box, 4); // box是一个RECT结构

如果机器的字节序与数据文件的字节序不匹配,这些宏被定义为执行某些函数,否则它们被定义为空:

#if defined(ENDIAN_CONVERSION) #define CQ_NTOHL(a) {a = ((a) >> 24) | (((a) & 0xff0000) >> 8) | (((a) & 0xff00) << 8) | ((a) << 24);} #define CQ_NTOHL_ARRAY(arr, num) {uint32 i; for(i = 0; i < num; i++) {CQ_NTOHL(arr[i]); }} #else #define CQ_NTOHL(a) #define CQ_NTOHL_ARRAY(arr, num) #endif

这种方法的优点是,如果ENDIAN_CONVERSION没有定义,它不会浪费CPU周期。而且代码可以保持其自然形式,一次读取整个结构体。

C语言的限制

这是用C语言所能做到的最好。但回想起80x86系列CPU有一个专门的指令来做字节序转换。经过一番搜索,确认它是BSWAP。对于ARM系列CPU,也有一个算法来加速它。

遗憾的是,C语言有一个限制。由于底层机器结构差异很大,一种高级语言如何利用它们的力量呢?

汇编语言的使用

在最简单的除法场景中,8086有一个指令DIV,它会将商和余数分别存储在AHAL中。但在C中,不得不写:

Quotient = a / b; Remainder = a % b;

看似计算了两次。但一个好的编译器能够优化它。

汇编语言的实现

认为有一个可能的解释。因为字节序转换所花费的时间被前面的IO操作所掩盖,似乎有点过头了。但这就是。;) 正在推动它的极限。

将为80486和ARM CPU编写汇编代码,并在以下部分告诉如何在VS和GCC下编译它。但首先,让声明将要编写的函数:

uint32 cq_ntohl(uint32 value); void cq_ntohl_array(uint32* arr, uint32 num); uint16 cq_ntohs(uint16 value); void cq_ntohs_array(uint16* arr, uint32 num); #define CQ_NTOHL(a) a = cq_ntohl(a); #define CQ_NTOHL_ARRAY(p, n) cq_ntohl_array(p, n); #define CQ_NTOHS(a) a = cq_ntohs(a); #define CQ_NTOHS_ARRAY(p, n) cq_ntohs_array(p, n);

至于实现,将为提供4个版本的它们:

  • Visual Studio. X86
  • Visual Studio. Smart Device
  • GCC arm-linux-gcc 内联汇编
  • GCC arm-linux-as 汇编器

源代码被打包成一个zip文件。在这里,将只解释如何在项目中使用它们以及需要注意的一些要点。

Visual Studio. X86

Visual Studio支持内联x86汇编。因此,只需将ec_x86.c编译添加到VS项目中即可。32位整数使用80486指令BSWAP进行转换,16位整数使用简单的8位右旋转。

uint32 cq_ntohl(uint32 a) { __asm { mov eax, a; bswap eax; } }

Visual Studio. Smart Device

Visual Studio不支持内联ARM汇编。因此,需要使用armasm.exe编译ec_arm.s文件:

armasm.exe ec_arm.s ec_arm.obj

然后像普通静态链接库一样将EXE与.obj文件链接。或者可以将.s添加到项目中并设置自定义构建步骤。但不会在这里解释细节。

GCC Arm-Linux-gcc

arm-linux-gcc支持内联汇编和.s文件。因此,可以选择使用ec_arm_linux.cec_arm_linux.s

没有汇编语言或汇编器的经验。只教了自己需要进行转换的东西,没有别的。所以如果有错误或遗漏,请原谅。

经验教训

从这次经历中,了解到机器指令和C语言之间存在巨大的差距。所以对编译器编写者更加敬佩。这确实是一份非常艰难的工作。也了解到C语言是平台独立的,编译器独立的。但有时,编写汇编语言会更快、更直接地完成工作。

关于字节序的基本概念

作者:Juan Carlos Cobas

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485