32bit与64bit程序移植

时间:2021-03-20 22:35:41

缘由
最近在移植32bit代码过程中,发现各种异常bug,排查和定位非常困难。很多都是编程习惯导致的。
132位与64位
(1)64bit CPU拥有更大的寻址能力,最大支持到16GB内存,而32bit只支持4G内存
(2)64位CPU一次可提取64位数据,比32位提高了一倍,理论上性能会提升1倍。但这是建立在64bit操作系统,64bit软件的基础上的。
2数据类型长度
32位编译器:
char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 4个字节
long long: 8个字节
unsigned long: 4个字节
64位编译器:
char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 8个字节
long long: 8个字节
unsigned long: 8个字节
以上占用字节数其实是针对c/c++语言而言的,对于java来说由于其JVM具有跨平台性因此java在32位和64位机下基本数据类型占字节数是一致的(这样才能达到跨平台通信)。
3如何编写32bit与64bit兼容的程序
统一数据类型定位,结合常见注意事项。
4如何移植32bit程序到64bit程序
通过添加编译选项,将编译警告当错误处理。
-Werror
Make all warnings into errors.
5常见注意事项
1、32位与64位数据模型之间的区别
32位应用的数据模型我们称作ILP32(之所以这样命名,是因为int,long,pointer都是32位),而64位应用则采用LP64模型(意味着long,pointer变成64位了,其他的不变)。在当前的32位环境下,我们可以认为int,long,pointer是等价的,因为它们占用相同的字节,于是就有很多混用的情况;但是到了64位的时代,long和Poniter的大小都改变了,原来的假设不再成立。
2、注意int和pointer转换
因为integer与pointer大小相同,所以32位代码中常常把pointer转换为int或者unsigned int,以便算术运算。为了移植,你可以把pointer转换为unsigned long,因为long和pointer都是等长的,无论是在ILP32或LP64。但是,为了使代码更清晰,推荐用uintptr_t,uintptr_t和intptr_t都需要包含头文件inttypes.h。
例如:下面代码在64位环境下编译出错:
char *p = &something;
p = (char *) ((int)p & PAGEOFFSET);
% cc ..
warning: conversion of pointer loses bits

改用uintptr_t后,无论是32位或者64位都没问题:
char *p = &something;
p = (char *) ((uintptr_t)p & PAGEOFFSET);
3、注意int和long转换
在ILP32中,可能从未对int和long加以区分,因此,混用的情况非常多,看下面代码:
1. int A = 0;
2. long B = 0;
3. long C = 0;
4. …
5. A = B + C;
6.
7.
8. % cc
9. warning: assignment of 64-bit integer to 32-bit integer
4、结构体字节对齐及大小问题
在结构体中,各个成员的起始地址都是对齐字节的倍数,例如:
1. struct T
2. {
3. int i;
4. long j;
5. int k;
6. char *p;
7. };
在ILP32中,sizeof(T)应该是16字节;在LP64中,应该是32!因为此时long/char *的对齐字节都变为8,为了保证满足对齐要求,i/k都被扩展为8字节了,又例如:
1. struct T
2. {
3. char *p;
4. long j;
5. int i;
6. int k;
7. };

此时,无需扩展,sizeof(T)=8+8+4+4=24.
5、注意union
例如:
1. union U
2. {
3. double _d;
4. long _l[2];
5. };
在ILP32中,两者大小相同都是8字节;移植到LP64,前者不变,后者为16字节,应改为:
1. union U
2. {
3. double _d;
4. int _l[2];
5. };
6、注意移位操作
无类型的整数常量就是 (unsigned) int 类型的,这可能会导致在位移时出现被截断的问题。

例如,在下面的代码中,a的最大值可以是 31。这是因为1 << a是 int 类型的:
1. long t = 1 << a;
要在 64 位系统上进行位移,应该使用1L,如下所示:
1. long t = 1L << a;
7、字符串格式化
函数printf 及其相关函数都可能成为问题的根源。例如,在 32 位系统上使用%d来打印 int 或 long 类型的值都可以,但是在 64 位平台上,这会导致将 long 类型的值截断成低 32 位的值。对于 long 类型的变量来说,正确的用法是%ld。

在下面的例子中,假设指针是 32 位的:
1. char *ptr = &something;
2. printf (%x\n”, ptr);
上面的代码在 64 位系统上会失败,它只会显示低 4 字节的内容。解决方案是使用%p,%p兼容ILP32和L64,另外就是作为目标的buffer必须够长:
1. char *ptr = &something;
2. printf (%p\n”, ptr);
8、类型定义
建议您不要使用 C/C++ 中那些在 64 位系统上会改变大小的数据类型来编写应用程序,而是使用一些类型定义或宏来显式地说明变量中所包含的数据的大小和类型,这些定义可以使代码的可移植性更好。例如,在sys/types.h中的数据类型,其大小会随ILP32或者LP64而变化:
1. * clock_t, which represents the system time in clock ticks
2. * dev_t, which is used for device numbers
3. * off_t, which is used for file sizes and offsets
4. * ptrdiff_t, which is the signed integral type for the result of subtracting two pointers
5. * size_t, which reflects the size, in bytes, of objects in memory
6. * ssize_t, which is used by functions that return a count of bytes or an error indication
7. * time_t, which counts time in seconds
例如,sizeof和strlen返回值都是size_t类型,当它们的返回值赋值给int类型的变量时,在ILP32位上没有问题,但在LP64上并且返回值大于2G时会发生截断:
1. int bufferSize = (int) sizeof (something);
2. int length = (int) strlen(str);

最好修改为:
1. size_t bufferSize = (size_t) sizeof (something);
2. size_t length = (size_t) strlen(str);
9、注意边缘效应
局部代码发生类型改变,可能导致其他代码发生64位转换,例如函数的返回值由Int变为sszie_t,则所有调用该函数并获取其返回值的地方,都有可能发生意想不到的64位转换。

10、注意long array 对效率的影响
大型Long/unsigned long数组,在LP64下,相比ILP32,效率非常低,所以,如果int就足够,尽量不要使用Long,这一点,对于pointer arrary同样适用。
11、第三方类库
程序中可能实用了第三方类库,此时要检查第三方类库是否可以在64位机上使用。
提醒:应用程序在32位机移植到64位机时,由于long指针等大小的改变,会发生数据截断等错误,应该将编译器的警告级别调高,反复调试,有可能一个小小的警告会让程序死掉。另外在编译时采用-errchk=longptr64选项可以检查出把long/pointer表达式转换为int的情况,包括显式转换。