移植软件到64位系统

转自:http://www.lingcc.com/2010/09/03/11176/
Linux是首个能用在64位处理器上的跨平台操作系统。64位系统在服务器和桌面领域非常常见。所以对于开发者来讲,如何让自己的软件能在32位、64位机上都能工作,是个需要重点考虑的问题。Linux系统使用LP64标准,即,除了指针和long整型是 64位外,普通整型仍未32位。因此,对于C语言程序,要重点检查。

64位系统有啥好处,为啥一定要移植?32位系统在大型应用上,给开发者带来和很多挑战,如数据库。还有那些开发者需要尽可能从硬件中获得加速和好处的程序。另外,科学计算通常需要浮点算术,一些财经领域的计算要求比普通浮点计算更高的精确度,更小的置信区间。而64位机恰好能满足这些需求。另外一方面,32位的指针只能标示4G的虚拟存储空间,虽然现在Linux内核能支持大于4G的内存识别,但实际上每个程序可用的内存空间仍然只有4G,若想突破这个局限,要么增加软件的复杂度,性能下降,要么采用64位系统。

另外,日期表示也是个问题,32位机上,使用32位的整数表示系统时间距离1970年1月1日的秒数,若超过2038年,这个整数就会溢出。但如果改用64位表示,起码我们这辈子就不用担心这个问题了。

总的来说,64位系统的优点:

   1. 64位应用可以直接访问2^64字节的内存。
   2. 64位机可以让单个文件大小达到4^63字节,这样大的数据库服务器就可以应用了。

对比64位和32位系统:

    * 32位Linux中,char 8bit, short 16bit, int 32bit, long 32bit, long long 64bit, pointer 32bit
    * 64位Linux中(以LP64标准为例),char 8bit, short 16bit, int 32bit, long 64bit, long long 64bit, pointer 64bit

32位和64位系统最大的区别主要在数据对象的大小方面。编译器一般会根据数据类型,做自然对齐处理。即,32位的数据类型在64位系统上,会按 32位对齐,64位数据类型则以64位边界对齐。对于结构体和共用体,在32位和64位系统上会有不同。 另外,因为64位系统也有不同的标准,如LP64、LLP64、ILP64.所以这些系统之间的区别也需要注意。

因为编译器要对数据做自然对齐,所以编译器需要使用填充(padding)来确保对齐。例如,下面的代码:
struct test {
int i1;
double d;
int i2;
long l;
}
在32位和64位系统上的表示如下:
结构体成员 在32位系统上的大小 在64位系统上的大小
struct test {
int i1; 32-bits 32-bits
32-bits 填充
double d; 64-bits 64-bits
int i2; 32 bits 32 bits
32-bits 填充
long l; 32 bits 64 bits
}; sizeof(test)=20 sizeof(test)=32
从32位系统到64位系统的移植:

容易出问题的几个地方:

    * 声明:尽量使用“L”和“U”后缀声明函数(译者注:“L”和“U”参考);能用无符号数就用无符号数;如果有在这两个平台上都为32位的整型,使用int定义;若需要某个变量在32位系统上32位,在64位机上64位,使用 long定义;对了对齐和性能考虑,使用int和long定义数值变量;使用字符指针和字符时,使用unsinged定义来避免8位符号扩展带来的问题。
    * 表达式:C/C++中,表达式都基于相关性,使用的操作符和算术转换规则。所以,请尽量遵循以下规则:两个有符号int的加保存到signed int中;一个int和一个long的加保存到long中;一个操作数为signed另一个为unsigned,使用unsigned保存;int和 double的加,结果保存到double中。
    * 赋值:因为指针、int和long在64位系统上大小不同,所以变量的赋值和使用方式可能会带来问题。不要把long赋值给int,这样可能会丢失高位信息;不要使用int来保存指针,因为64位机上,指针是64位的,int是32位的;同时也不能用指针保存int;基本上这类问题都是因为数据表示,int、unsigned、long。尽量使用表示范围较大的类型,能避免不必要到麻烦。
    * 数据常数:16进制的常数通常用于做标记或者指定特定位的值。比如常数OXFFFFFFFFL声明signed long,在32位系统上,所有位为一,但64位系统上只有最后32位设置了,结果是OX00000000FFFFFFFF.对于有符号数,这明显是不对的。如果你想把所有的位置1,可以使用long x = -1L; 另外,通过移位来设置最高位的时候也要注意,采用1L << ((sizeof(long) * -1); 可移植性更强。
    * 大小尾端:大小尾端说白了,是一个数据如何存的问题。小尾端把数据的低位存在存储位置的低位。大尾端相反。当使用位标记,对某个数据对象用间接指针切分的时候,要特别注意大小尾端的问题。网络子系统提供了htonl、ntohl、htons、ntohs用来做大小尾端转换。但这些是针对32位和 16位数据的.另外,还有一些宏,bswap_16, bswap_32, bswap_64来做转换。
    * 类型定义:建议不要使用那些在32位、64位机上大小不同的数据类型,而是采用定长的数据类型。如int32_t, uint32_t(确定长度)、size_t(传参给malloc和free的时候使用sizeof),另外还有ptrdiff_t、intptr_t、 uintptr_t。
    * 移位:没有指定类型的整型常数,默认int或者unsigned int型。在移位的时候要小心。
    * 格式化字符串:printf和其他相关函数也有问题。32位平台上,使用%d来打印int和long都没问题,但64位平台上,long会被切去高32位,所以最好使用%ld来打印。同样,当一个小整型(char,short,int)传给printf时,会被展开成64位,所以最好使用%p而不是%x来输出16进制的数。%p是打印一个指针地址,%x只是以普通的十六进制打印。
    * 函数传参:在函数传参时,要注意一些隐含的强制类型转换。还有函数的返回值。

结论

基于性能、性价比和扩展性的考虑,很多平台提供了64位系统支持。32位系统对最大4G虚拟内存的限制,让许多公司不得不考虑64位系统。知道把系统移植到64位平台上,一些注意事项,将让你写出可移植而且高效的代码。

PS:这篇文章是自己学习之用,IBM有更完整的翻译版本,请参考:http://www.ibm.com/developerworks/cn/linux/l-port64.html

参考:

    * http://www.ibm.com/developerworks/library/l-port64.html

猜你喜欢

转载自soft82-126-com.iteye.com/blog/1340772