在跨平台项目开发中,经常会遇到一个古老的问题——字节序转换。如果一个文件是在小端字节序的机器上生成的,那么一个整数255可能被存储为:
ff 00 00 00
但是当它被读入内存时,在不同的平台上其值会有所不同。这将导致移植问题。
在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语言所能做到的最好。但回想起80x86系列CPU有一个专门的指令来做字节序转换。经过一番搜索,确认它是BSWAP。对于ARM系列CPU,也有一个算法来加速它。
遗憾的是,C语言有一个限制。由于底层机器结构差异很大,一种高级语言如何利用它们的力量呢?
在最简单的除法场景中,8086有一个指令DIV
,它会将商和余数分别存储在AH
和AL
中。但在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个版本的它们:
源代码被打包成一个zip文件。在这里,将只解释如何在项目中使用它们以及需要注意的一些要点。
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不支持内联ARM汇编。因此,需要使用armasm.exe
编译ec_arm.s
文件:
armasm.exe ec_arm.s ec_arm.obj
然后像普通静态链接库一样将EXE与.obj
文件链接。或者可以将.s
添加到项目中并设置自定义构建步骤。但不会在这里解释细节。
arm-linux-gcc支持内联汇编和.s
文件。因此,可以选择使用ec_arm_linux.c
或ec_arm_linux.s
。
没有汇编语言或汇编器的经验。只教了自己需要进行转换的东西,没有别的。所以如果有错误或遗漏,请原谅。
从这次经历中,了解到机器指令和C语言之间存在巨大的差距。所以对编译器编写者更加敬佩。这确实是一份非常艰难的工作。也了解到C语言是平台独立的,编译器独立的。但有时,编写汇编语言会更快、更直接地完成工作。
关于字节序的基本概念
作者:Juan Carlos Cobas