Linux 是同时支持32-bits和64-bit跨平台的操作系统。随着双核机器越来越多,因此有很多应用需要适应不同的操作系统,也就是说需要同时兼容32位和64位。
首先我们来看32-bits和64-bits对C语言标准支持的不同。从表一可以看,通常LINUX32-bit 使用ILP32,而64-bits使用LP64标准,在32-bits标准中,int, long,pointer都是分配32it,
而64-bits系统中则不一样,pointer和long 是64位,但是int是32位。
由于这两种系统的主要不同点是在long和point上,而这两种数据类型又是开发时经常要进行声明赋值,特别是一些对一些常见结构体数据大小的计算上,差异还是非常明显的。如下面的数据结构:
struct test {
int i1;
double d;
int i2;
long l;
}
注意这时候的sizeof(test) 在32bit和64bit上是分了不同大小空间的。32bit 上是4+4+4+4+4=20而64bit是8(空半)+8+8(空半)+8=32
Linux 应用兼容64bit/32bit的一些基本原则
1. 声明
如果这个变量在32bit上要求32bit,但是在64bit上要求64bit,应该定义成long型或者如果这个变量指的是一个地址,这里就需要用size_t。
如果有以下这样的转换也不行的,g_shm_mem_ptr 是指针,在32bit可以用int,但到64位需要用long,
if (g_shm_mem_ptr != 0 && (int) g_shm_mem_ptr != -1)改成 if (g_shm_mem_ptr != 0 && (long) g_shm_mem_ptr != -1)
如果这个变量要求分配的空间在不同系统上是一样的,因些对声明long型改成声明int型进行兼容。
其它像unsigned long int实际在32位系统就是unsigned long. 取值范围是0~4294967295(232 -1)
2. 赋值
不要使用不同类型的变量直接赋值。如
o int 与long 转换
32-bits |
64-bits |
|
int i; |
32bit |
32bit |
long l ; |
32bit |
64bit |
i =l ; |
OK |
Error |
o int 与pointer
32-bits |
64-bits |
|
unsigned int i,* ptr; |
32bit |
32bit |
i=(unsigned)ptr; |
OK |
Error |
*ptr 可以是32bit,但ptr 一定是64bit |
o pointer 与(int *)
32-bits |
64-bits |
|
int * ptr; |
32bit |
32bit |
int i; |
32bit |
32bit |
ptr =(int *)i; |
OK |
Error |
3. 字符标志位用来表示特定含义
如用0xFFFFFFF 每一个字符位来表示特定含义的用法,需要注意在64位它就变成0x00000000FFFFFFFF,计算时特别要小心。如果要声明0xFFFFFFFFFFFFFF可用long x=-L来声明。
4. 字节序大小头不同
我们知道,字节序分成big-endian 和little-endian. Little-endian 意味着较低内存地址用来存储不重要数据,(所谓不重要数据通常指的是数据的未位,如0x12345678中的5678。big-endian 则相反。如果以内存的排列顺序(从低到高由左向右来看),就会出现高位数据放在前面变成56781234。采用little-endian通常都是intel体系架构主机。
另外,32-bits是两个字节两个字节(16bit)的分配,而64-bits是四个字节(32bit)一次分配。如0x12345678在32bit机器上
0x12345678 big-endian
32-bits |
64-bits |
|
3 |
0x78 |
|
2 |
0x56 |
|
1 |
0x34 |
0x5678 |
0 |
0x12 |
0x1234 |
0x12345678 little-endian
32-bits |
64-bits |
|
3 |
0x12 |
|
2 |
0x34 |
|
1 |
0x56 |
0x1234 |
0 |
0x78 |
0x5678 |
在两台不同主机进行数据传输的网络字节序目前采用的是big-endian.
从开发的角度来说,由于glibc库已经实现了内部的封装,大家可以查看以下库文件
/usr/include/bits/endian.h
/usr/include/endian.h
/usr/include/bits/byteswap.h
/usr/include/byteswap.h
我们只要使用与机器无关的函数如ntohl等就可以了。
通常意义上我们都使用函数htonl和ntohl及htons,ntohs进行转换。注意这些函数都不能对64位数据进行转换,
#if __WORDSIZE == 64
#if __BYTE_ORDER == __BIG_ENDIAN
/* The host byte order is the same as network byte order,
so these functions are all just identity. */
#define ntohlong(x) (x)
#define htonlong(x) (x)
#else
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ntohlong(x) __bswap_64 (x)
#define htonlong(x) __bswap_64 (x)
#endif
#endif
#else
#define ntohlong(x) ntohl (x)
#define htonlong(x) htonl (x)
#endif
#endif
5. 类型重定义
通常一些定义类型有一些跨平台作用,如size_t或者一些明确大小的如int32_t,uint32_t。通常如
int buffersize = (int)sizeof(something);最好的方法是
size_t buffersize = (size_t)sizeof(something)
6. 移位算法
对一些未显示指明类型的常量通常会隐含风险,如12这实际上是int类型。如果long i =12 这实际上是long i =(int)12在32位系统,但在64位系统上long i = (int)12,就会带来问题。
需要声明long i = 1L;;
7. 格式化输出printf
printf()进行格式输出时,不同参数的数据类型在32bit和64 bit下输出结果是完全不一样的。这样的隐藏错误有时很难发现,所以一定要注意检查是否需要修改相应的格式字符串,否则编译器连警告信息都不会出现。
比喻在32bits平台,%d可以打印long也可以是int,但在64位不行,需要使用%ld或者%u
printf("Indx %dld",strtol(tableInfo->tbl_index,NULL,0));
%d和%u的区别
unsigned int dwValue;
printf(“%d”, dwValue);
在dwValue的值大于0x7FFFFFFF时,输出的结果会变成负数。
正确的程序应该为:
printf(“%u”, dwValue);
%x 在32bit用来输出地址,但是在64 上不用,必须用%p
%d在32bit用来输出int and long,在64上不行,必须用%ld