32bit-64bit porting work注意事项64位服务器逐步普及,各条产品线对64位升级的需求也不断加大。
在本文中,主要讨论向64位平台移植现有32位代码时,应注意的一些细小问题。
什么样的程序需要升级到64位?理论上说,64位的操作系统,对32位的程序具有良好的兼容性,即使全部换成64位平台,依然可以良好的运行32位的程序。
因此,许多目前在32位平台上运行良好的程序也许不必移植,有选择,有甄别的进行模块的升级,对我们工作的展开,是有帮助的。
什么样的程序需要升级到64位呢?除非程序有以下要求:●需要多于4GB的内存。
●使用的文件大小常大于2GB。
●密集浮点运算,需要利用64位架构的优势。
●能从64位平台的优化数学库中受益。
ILP32和LP64数据模型32位环境涉及"ILP32"数据模型,是因为C数据类型为32位的int、long、指针。
而64位环境使用不同的数据模型,此时的long和指针已为64位,故称作"LP64"数据模型。
下面由上表我们可以看出,32位到64位的porting工作,主要就是处理长度变化所引发的各种问题。
在32位平台上很多正确的操作,在64位平台上都不再成立。
例如:long->int等,会出现截断问题等。
下面将详细阐述具体遇到的问题,并给出修改策略。
截断问题截断问题是在32-64porting工作中最容易遇到的问题。
部分的截断问题能够被编译器捕捉到,采用-Wall –W进行编译,永远没有坏处。
这种问题处理方法也非常简单,举个例子来说:long mylong;(void) scanf("%d", &mylong);// warning: int format, different type arg (arg 2)long mylong;(void) scanf("%ld", &mylong);// ok但有很多情况下,一些截断性问题并不能被良好的诊断出来。
例如:long a;int b;b = a;在这种情况下,编译器会直接进行转换(截断处理),编译阶段不报任何警告。
当a的数据范围在2G范围内时,不会出问题,但是超出范围,数据将出现问题。
另外,采用了强制转换的方式,使一些隐患被保留了下来,例如:long mylong;(void) scanf("%d",(int*)&mylong);//编译成功,但mylong的高位未被赋值,有可能导致问题。
采用pclint可以有效的检查这种问题,但是,在繁多的warning 中,找到需要的warning,并不是一件容易的事情。
因此,在做平台移植的时候,对于截断问题,最根本的还是逐行阅读代码,详细检测。
在编码设计的时候,尽量保持使用变量类型的一致性,避免发生截断问题。
建议:在接口以及数据结构的定义中不要使用指针,long,以及用long定义的类型(size_t, ssize_t, off_t, time_t),由于字长的变化,这些类型不能32/64位兼容。
一个讨厌的类型size_t:在32bit平台上,它的原形是unsigned int,而在64bit平台上,它的原形式unsigned long。
这导致在printf等使用时:无论使用%u或者%lu都会有一个平台报warning。
目前我们的解决办法是:采用%lu打印,并且size_t强制转换为unsinged long。
在小尾字节序(Little-endian)的系统中,这种转换是安全的。
常量有效性问题那些以十六进制或二进制表示的常量,通常都是32位的。
例如,无符号32位常量0xFFFFFFFF通常用来测试是否为-1;#define INV ALID_POINTER_V ALUE 0xFFFFFFFF然而,在64位系统中,这个值不是-1,而是4294967295;在64位系统中,-1正确的值应为0xFFFFFFFFFFFFFFFF。
要避免这个问题,在声明常量时,使用const,并且带上signed 或unsigned。
例如:const signed int INV ALID_POINTER_V ALUE = 0xFFFFFFFF;上面一行代码将会在32位和64位系统上都运行正常。
或者,根据需要适当地使用“L”或“U”来声明整型常量。
又比如对最高位的设置,通常我们的做法是定义如下的常量0x80000000,但是可移植性更好的方法是使用一个位移表达式:1L << ((sizeof(long) * 8) - 1);参数问题在参数的数据类型是由函数原型定义的情况中,参数应该根据标准规则转换成这种类型。
在参数类型没有指定的情况中,参数会被转换成更大的类型。
在64 位系统上,整型被转换成64 位的整型值,单精度的浮点类型被转换成双精度的浮点类型。
如果返回值没有指定,那么函数的缺省返回值是int 类型的。
避免将有符号整型和无符号整型的和作为long 类型传递(见符号扩展问题)请看下面的例子:long function (long l);int main () {int i = -2;unsigned k = 1U;long n = function (i + k);}上面这段代码在 64 位系统上会失败,因为表达式 (i + k) 是一个无符号的 32 位表达式,在将其转换成 long 类型时,符号并没有得到扩展。
解决方案是将一个操作数强制转换成 64 位的类型。
扩充问题(指针范围越界)扩充问题与代码截断问题刚好相反。
请看下面的例子:int baidu_gunzip(char* inbuf,int len,char* outbuf,int* size){……ret=bd_uncompress((Byte*)outbuf,(uLongf*)size,(Byte*)(inbuf+beginpos),inlen);}这是ullib库中baidugz模块的一段代码。
在这段代码中,将int型的指针改为long型的指针传递给了bd_uncompress函数。
在32位系统中,由于int与long都是32bit,程序没有任何问题,但在64位系统中,将导致指针控制的范围在调用函数中扩展为原来的2倍,这将有可能导致程序出core,或指示的值不正确。
这种问题比较隐蔽,很难发现,但危害极大,需要严格注意。
解决方法:加强对指针型参数的检查,看是否有范围扩充的问题。
符号扩展问题要避免有符号数与无符号数的算术运算。
在把int与long数值作对比时,此时产生的数据提升在LP64和ILP32中是有差异的。
因为是符号位扩展,所以这个问题很难被发现,只有保证两端的操作数均为signed或均为unsigned,才能从根本上防止此问题的发生。
例如:long k;int i = -2;unsigned int j = 1;k = i + j;printf("Answer: %ld\n", k);你无法期望例2中的答案是-1,然而,当你在LP64环境中编译此程序时,答案会是4294967295。
原因在于表达式(i+j)是一个unsigned int表达式,但把它赋值给k时,符号位没有被扩展。
要解决这个问题,两端的操作数只要均为signed或均为unsigned就可。
像如下所示:k = i + (int) j在 C/C++ 中,表达式是基于结合律、操作符的优先级和一组数学计算规则的。
要想让表达式在 32 位和 64 位系统上都可以正确工作,请注意以下规则:●两个有符号整数相加的结果是一个有符号整数。
●int 和 long 类型的两个数相加,结果是一个 long 类型的数。
●如果一个操作数是无符号整数,另外一个操作数是有符号整数,那么表达式的结果就是无符号整数。
●int 和 doubule 类型的两个数相加,结果是一个 double 类型的数。
此处 int 类型的数在执行加法运算之前转换成 double 类型。
将字符指针和字符字节声明为无符号类型的,这样可以防止 8 位字符的符号扩展问题。
联合体问题(Union)当联合本中混有不同长度的数据类型时,如果单独使用里面定义的成员,一般没有问题。
但在一些复杂的操作中,例如几种类型的混用,可能会导致问题。
如例3是一个常见的开源代码包,可在ILP32却不可在LP64环境下运行。
代码假定长度为2的unsigned short数组,占用了与long同样的空间,可这在LP64平台上却不正确。
union{unsigned long bytes;unsigned short len[2];} size;正确的方法是检查是否对结构体有特殊的应用,如果有,那么需要在所有代码中仔细检查联合体,以确认所有的数据成员在LP64中都为同等长度。
对齐问题现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。
一些平台对某些特定类型的数据只能从某些特定地址开始存取。
其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。
比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32 位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。
显然在读取效率上下降很多。
这也是空间和时间的博弈。
g++默认对齐方式是按结构中相临数据类型最长的进行在32位上,这些类型默认使用一个机器字(4字节)对齐。
在64位上,这些类型默认使用最大两个机器字(8×2=16字节)对齐(按16字节对齐会有好处么?估计于编译器的具体实现相关)举两个例子说明一下:struct asdf {int a;long long b;};这个结构在32bit操作系统中,sizeof(asdf) = 12,在64bit操作系统中,sizeof(asdf)=16struct asdf {int a;long double b;};这个结构在32bit操作系统中,sizeof(asdf) = 16,在64bit操作系统中,sizeof(asdf)=32.这里需要说明的是,32位sizeof(long double) = 12, 64位sizeof(long double)=16在跨平台的网络传输过程中,或者文件存取过程中,我们经常会遇到与数据对齐相关的问题,并且为此烦恼不已。