现在的位置: 首页 > 综合 > 正文

从 32 位系统移植到 64 位系统

2013年06月17日 ⁄ 综合 ⁄ 共 4537字 ⁄ 字号 评论关闭

要想让您的代码在 32 位和 64 位系统上都可以工作,请注意以下有关声明的用法:

  • 根据需要适当地使用 “L” 或 “U” 来声明整型常量。
  • 确保使用无符号整数来防止符号扩展的问题。
  • 如果有些变量在这两个平台上都需要是 32 位的,请将其类型定义为 int。
  • 如果有些变量在 32 位系统上是 32 位的,在 64 位系统上是 64 位的,请将其类型定义为 long。
  • 为了对齐和性能的需要,请将数字变量声明为 int 或 long 类型。不要试图使用 char 或 short 类型来保存字节。
  • 将字符指针和字符字节声明为无符号类型的,这样可以防止 8 位字符的符号扩展问题。

表达式

在 C/C++ 中,表达式是基于结合律、操作符的优先级和一组数学计算规则的。要想让表达式在 32 位和 64 位系统上都可以正确工作,请注意以下规则:

  • 两个有符号整数相加的结果是一个有符号整数。
  • int 和 long 类型的两个数相加,结果是一个 long 类型的数。
  • 如果一个操作数是无符号整数,另外一个操作数是有符号整数,那么表达式的结果就是无符号整数。
  • int 和 doubule 类型的两个数相加,结果是一个 double 类型的数。此处 int 类型的数在执行加法运算之前转换成 double 类型。

赋值

由于指针、int 和 long 在 64 位系统上大小不再相同了,因此根据这些变量是如何赋值和在应用程序中使用的,可能会出现问题。下面是有关赋值的一些技巧:

  • 不要交换使用 int 和 long 类型,因为这可能会导致高位数字被截断。例如,不要做下面的事情:

    int i;
    long l;
    i = l;
  • 不要使用 int 类型来存储指针。下面这个例子在 32 位系统上可以很好地工作,但是在 64 位系统上会失败,这是因为 32 位整数无法存放 64 位的指针。例如,不要做下面的事情:
    unsigned int i, *ptr;
    i = (unsigned) ptr;
  • 不要使用指针来存放 int 类型的值。例如,不要做下面的事情;
    int *ptr;
    int i;
    ptr = (int *) i;
    
  • 如果在表达式中混合使用无符号和有符号的 32 位整数,并将其赋值给一个有符号的 long 类型,那么将其中一个操作数转换成 64 位的类型。这会导致其他操作数也被转换成 64 位的类型,这样在对表达式进行赋值时就不需要再进行转换了。另外一种解决方案是对整个表达式进行转换,这样就可以在赋值时进行符号扩展。例如,考虑下面这种用法可能会出现的问题:
    long n;
    int i = -2;
    unsigned k = 1;
    n = i + k;
    

    从数学计算上来说,上面这个黑体显示的表达式的结果应该是 -1 。但是由于表达式是无符号的,因此不会进行符号扩展。解决方案是将一个操作数转换成 64 位类型(下面的第一行就是这样),或者对整个表达式进行转换(下面第二行):

     

    n = (long) i + k;
    n = (int) (i + k);
    

     

数字常量

16 进制的常量通常都用作掩码或特殊位的值。如果一个没有后缀的 16 进制的常量是 32 位的,并且其高位被置位了,那么它就可以作为无符号整型进行定义。

例如,常数 OxFFFFFFFFL 是一个有符号的 long 类型。在 32 位系统上,这会将所有位都置位(每位全为 1),但是在 64 位系统上,只有低 32 位被置位了,结果是这个值是 0x00000000FFFFFFFF。

如果我们希望所有位全部置位,那么一种可移植的方法是定义一个有符号的常数,其值为 -1。这会将所有位全部置位,因为它采用了二进制补码算法

long x = -1L;

可能产生的另外一个问题是最高位的设置。在 32 位系统上,我们使用的是常量 0x80000000。但是可移植性更好的方法是使用一个位移表达式:

1L << ((sizeof(long) * 8) - 1);

Endianism

Endianism 是指用来存储数据的方法,它定义了整数和浮点数据类型中是如何对字节进行寻址的。

Little-endian 是将低位字节存储在内存的低地址中,将高位字节存储在内存的高地址中。

Big-endian 是将高位字节存储在内存的低地址中,将低位字节存储在内存的高地址中。

表 3 给出了一个 64 位长整数的布局示例。

表 3. 64 位 long int 类型的布局

  低地址             高地址
Little endian Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 Byte 7
Big endian Byte 7 Byte 6 Byte 5 Byte 4 Byte 3 Byte 2 Byte 1 Byte 0

例如,32 位的字 0x12345678 在 big endian 机器上的布局如下:

表 4. 0x12345678 在 big-endian 系统上的布局

内存偏移量 0 1 2 3
内存内容 0x12 0x34 0x56 0x78

如果将 0x12345678 当作两个半字来看待,分别是 0x1234 和 0x5678,那么就会看到在 big endian 机器上是下面的情况:

表 5. 0x12345678 在 big-endian 系统上当作两个半字来看待的情况

内存偏移量 0 2
内存内容 0x1234 0x5678

然而,在 little endian 机器上,字 0x12345678 的布局如下所示:

表 6. 0x12345678 在 little-endian 系统上的布局

内存偏移量 0 1 2 3
内存内容 0x78 0x56 0x34 0x12

类似地,两个半字 0x1234 和 0x5678 如下所示:

表 7. 0x12345678 在 little-endian 系统上作为两个半字看到的情况

内存偏移量 0 2
内存内容 0x3412 0x7856

下面这个例子解释了 big endian 和 little endian 机器上字节顺序之间的区别。

下面的 C 程序在一台 big endian 机器上进行编译和运行时会打印 “Big endian”,在一台 little endian 机器上进行编译和运行时会打印 “Little endian”。

清单 2. big endian 与 little endian

#include <stdio.h>
main () {
int i = 0x12345678;
if (*(char *)&i == 0x12)
printf ("Big endian/n");
else if (*(char *)&i == 0x78)
    		printf ("Little endian/n");
}

Endianism 在以下情况中非常重要:

  • 使用位掩码时
  • 对象的间接指针地址部分

在 C 和 C++ 中有位域来帮助处理 endian 的问题。我建议使用位域,而不要使用掩码域或 16 进制的常量。有几个函数可以用来将 16 位和 32 位数据从 “主机字节顺序” 转换成 “网络字节顺序”。例如,htonl (3)ntohl (3) 用来转换 32 位整数。类似地,htons (3)ntohs (3) 用来转换 16 位整数。然而,对于 64 位整数来说,并没有标准的函数集。但是在 big endian 和 little endian 系统上,Linux 都提供了下面的几个宏:

  • bswap_16
  • bswap_32
  • bswap_64

类型定义

建议您不要使用 C/C++ 中那些在 64 位系统上会改变大小的数据类型来编写应用程序,而是使用一些类型定义或宏来显式地说明变量中所包含的数据的大小和类型。有些定义可以使代码的可移植性更好。

  • ptrdiff_t: 
    这是一个有符号整型,是两个指针相减后的结果。
  • size_t: 
    这是一个无符号整型,是执行 sizeof 操作的结果。这在向一些函数(例如 malloc (3))传递参数时使用,也可以从一些函数(比如 fred (2))中返回。
  • int32_tuint32_t 等: 
    定义具有预定义宽度的整型。
  • intptr_t 和 uintptr_t: 
    定义整型类型,任何有效指针都可以转换成这个类型。

例 1:

在下面这条语句中,在对 bufferSize 进行赋值时,从 sizeof 返回的 64 位值被截断成了 32 位。

int bufferSize = (int) sizeof (something);

解决方案是使用 size_t 对返回值进行类型转换,并将其赋给声明为 size_t 类型的 bufferSize,如下所示:

size_t bufferSize = (size_t) sizeof (something);

例 2:

在 32 位系统上,int 和 long 大小相同。由于这一点,有些开发人员会交换使用这两种类型。这可能会导致指针被赋值给 int 类型,或者反之。但是在 64 位的系统上,将指针赋值给 int 类型会导致截断高 32 位的值。

解决方案是将指针作为指针类型或为此而定义的特殊类型进行存储,例如 intptr_t 和 uintptr_t

位移

无类型的整数常量就是 (unsigned) int 类型的。这可能会导致在位移时出现被截断的问题。

例如,在下面的代码中,a 的最大值可以是 31。这是因为 1 << a 是 int 类型的。

long t = 1 << a;

要在 64 位系统上进行位移,应该使用 1L,如下所示:

long t = 1L << a;

字符串格式化

函数 printf (3) 及其相关函数都可能成为问题的根源。例如,在 32 位系统上,使用 %d 来打印 int 或 long 类型的值都可以,但是在 64 位平台上,这会导致将 long 类型的值截断成低 32 位的值。对于 long 类型的变量来说,正确的用法是 %ld

类似地,当一个小整数(char、short、int)被传递给 printf (3) 时,它会扩展成 64 位的,符号会适当地进行扩展。在下面的例子中,printf (3) 假设指针是 32 位的。

char *ptr = &something;
printf (%x/n", ptr);

上面的代码在 64 位系统上会失败,它只会显示低 4 字节的内容。

这个问题的解决方案是使用 %p,如下所示;这在 32 位和 64 位系统上都可以很好地工作:

char *ptr = &something;
printf (%p/n", ptr);

函数参数

在向函数传递参数时需要记住几件事情:

  • 在参数的数据类型是由函数原型定义的情况中,参数应该根据标准规则转换成这种类型。
  • 在参数类型没有指定的情况中,参数会被转换成更大的类型。
  • 在 64 位系统上,整型被转换成 64 位的整型值,单精度的浮点类型被转换成双精度的浮点类型。
  • 如果返回值没有指定,那么函数的缺省返回值是 int 类型的。

在将有符号整型和无符号整型的和作为 long 类型传递时就会出现问题。考虑下面的情况:

清单 3. 将有符号整型和无符号整型的和作为 long 类型传递

long function (long l);
int main () {
	int i = -2;
	unsigned k = 1U;
	long n = function (i + k);
}

上面这段代码在 64 位系统上会失败,因为表达式 (i + k) 是一个无符号的 32 位表达式,在将其转换成 long 类型时,符号并没有得到扩展。解决方案是将一个操作数强制转换成 64 位的类型。

在基于寄存器的系统上还有一个问题:系统采用寄存器而不是堆栈来向函数传递参数。考虑下面的例子:

float f = 1.25;
printf ("The hex value of %f is %x", f, f);

在基于堆栈的系统中,这会打印对应的 16 进制值。但是在基于寄存器的系统中,这个 16 进制的值会从一个整数寄存器中读取,而不是从浮点寄存器中读取。

解决方案是将浮点变量的地址强制转换成一个指向整型类型的指针,如下所示:

printf ("The hex value of %f is %x", f, *(int *)&f);

抱歉!评论已关闭.