设为首页 友情链接
在线留言 发表文章
加入收藏 广告联系

刺猬首页

| 专案技术 | 网络技术 | 图形图象 | 网络编程 | 网页设计 | 操作系统 | 服务器 | 技术白皮书 | 在线实验室 | 刺猬论坛 |
小说专版  | 数据库 | 设计赏析 | 存储频道 | 网络安全 | 私服架设 |  Solaris | 网站评估 | PC维护技巧 | 下载中心 | 博 客 |
专   题: | Linux | java | cisco | 防病毒 | 刀片 | SOA | iscsi | ASP.NET | SQL | Oracle |
您现在的位置: IT公社 IT community >> Linux专题 >> Linux 软件开发 >> 教程正文 用户登录 新用户注册
专 题 栏 目
最 新 热 门
最 新 推 荐
相 关 文 章
Linux下的硬件驱动——U…
实战linux内核编译
Linux内核参数(二)
linux内核参数(一)
Linux 2.4.20 以后内核的…
菜鸟浅谈Linux内核编译过…
Linux操作系统之快速内存…
Linux操作系统的内存管理…
从红旗5.0说起——看Lin…
Linux Kernel畸形ULE拒绝…
  Linux应用程序移植到64位系统         
Linux应用程序移植到64位系统
 

随着 64 位体系结构的普及,针对 64 位系统准备好您的 Linux? 软件已经变得比以前更为重要。在本文中,您将学习如何在进行语句声明、赋值、位移、类型转换、字符串格式化以及更多操作时,防止出现可移植性缺陷。

    Linux 是可以使用 64 位处理器的跨平台操作系统之一,现在 64 位的系统在服务器和桌面端都已经非常常见了。很多开发人员现在都面临着需要将自己的应用程序从 32 位环境移植到 64 位环境中。随着 Intel? Itanium? 和其他 64 位处理器的引入,使软件针对 64 位环境做好准备变得日益重要了。

    与 UNIX? 和其他类 UNIX 操作系统一样,Linux 使用了 LP64 标准,其中指针和长整数都是 64 位的,而普通的整数则依然是 32 位的。尽管有些高级语言并不会受到这种类型大小不同的影响,但是另外一些语言(例如 C 语言)却的确会受到这种影响。

    将应用程序从 32 位系统移植到 64 位系统上的工作可能会非常简单,也可能会非常困难,这取决于这些应用程序是如何编写和维护的。很多琐碎的问题都可能导致产生问题,即使在一个编写得非常好的高度可移植的应用程序中也是如此,因此本文将对这些问题进行归纳总结,并给出解决这些问题的一些方法建议。

    64 位的优点

    32 位平台有很多限制,这些限制正在阻碍大型应用程序(例如数据库)开发人员的工作进展,尤其对那些希望充分利用计算机硬件优点的开发人员来说更是如此。科学计算通常要依赖于浮点计算,而有些应用程序(例如金融计算)则需要一个比较狭窄的数字范围,但是却要求更高的精度,其精度高于浮点数所提供的精度。64 位数学运算提供了这种更高精度的定点数学计算,同时还提供了足够的数字范围。现在在计算机业界中有很多关于 32 位地址空间所表示的地址空间的讨论。32 位指针只能寻址 4GB 的虚拟地址空间。我们可以克服这种限制,但是应用程序开发就变得非常复杂了,其性能也会显著降低。

    在语言实现方面,目前的 C 语言标准要求 “long long” 数据类型至少是 64 位的。然而,其实现可能会将其定义为更大。

    另外一个需要改进的地方是日期。在 Linux 中,日期是使用 32 位整数来表示的,该值所表示的是从 1970 年 1 月 1 日至今所经过的秒数。这在 2038 年就会失效。但是在 64 位的系统中,日期是使用有符号的 64 位整数表示的,这可以极大地扩充其可用范围。

    总之,64 位具有以下优点:

   1. 64 位的应用程序可以直接访问 4EB 的虚拟内存,Intel Itanium 处理器提供了连续的线性地址空间。

   2. 64 位的 Linux 允许文件大小最大达到 4 EB(2 的 63 次幂),其重要的优点之一就是可以处理对大型数据库的访问。

    Linux 64 位体系结构

    不幸的是,C 编程语言并没有提供一种机制来添加新的基本数据类型。因此,提供 64 位的寻址和整数运算能力必须要修改现有数据类型的绑定或映射,或者向 C 语言中添加新的数据类型。

    表 1. 32 位和 64 位数据模型  ILP32LP64LLP64ILP64char8888short16161616int32323264long32643264long long64646464指针32646464    这 3 个 64 位模型(LP64、LLP64 和 ILP64)之间的区别在于非浮点数据类型。当一个或多个 C 数据类型的宽度从一种模型变换成另外一种模型时,应用程序可能会受到很多方面的影响。这些影响主要可以分为两类:

    数据对象的大小。编译器按照自然边界对数据类型进行对齐;换而言之,32 位的数据类型在 64 位系统上要按照 32 位边界进行对齐,而 64 位的数据类型在 64 位系统上则要按照 64 位边界进行对齐。这意味着诸如结构或联合之类的数据对象的大小在 32 位和 64 位系统上是不同的。

    基本数据类型的大小。通常关于基本数据类型之间关系的假设在 64 位数据模型上都已经无效了。依赖于这些关系的应用程序在 64 位平台上编译也会失败。例如,sizeof (int) = sizeof (long) = sizeof (pointer) 的假设对于 ILP32 数据模型有效,但是对于其他数据模型就无效了。

    总之,编译器要按照自然边界对数据类型进行对齐,这意味着编译器会进行 “填充”,从而强制进行这种方式的对齐,就像是在 C 结构和联合中所做的一样。结构或联合的成员是根据最宽的成员进行对齐的。清单 1 对这个结构进行了解释。

    清单 1. C 结构


struct test {

	int i1;

	double d;

	int i2;

	long l;

}


    表 2 给出了这个结构中每个成员的大小,以及这个结构在 32 位系统和 64 位系统上的大小。

    表 2. 结构和结构成员的大小


结构成员在 32 位系统上的大小在 64 位系统上的大小struct test {  int i1;32 位32 位 32 位填充double d;64 位64 位int i2;32 位32 位 32 位填充long l;32 位64 位};结构大小为 20 字节结构大小为 32 字节
    注意,在一个 32 位的系统上,编译器可能并没有对变量 d 进行对齐,尽管它是一个 64 位的对象,这是因为硬件会将其当作两个 32 位的对象进行处理。然而,64 位的系统会对 d 和 l 都进行对齐,这样会添加两个 4 字节的填充。

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

    本节介绍如何解决一些常见的问题:

    声明表达式赋值数字常数Endianism类型定义位移字符串格式化函数参数

    声明

    要想让您的代码在 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 endianByte 0Byte 1Byte 2Byte 3Byte 4Byte 5Byte 6Byte 7Big endianByte 7Byte 6Byte 5Byte 4Byte 3Byte 2Byte 1Byte 0
    例如,32 位的字 0x12345678 在 big endian 机器上的布局如下:

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


内存偏移量0123内存内容0x120x340x560x78

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

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


内存偏移量02内存内容0x12340x5678
    然而,在 little endian 机器上,字 0x12345678 的布局如下所示:

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

内存偏移量0123内存内容0x780x560x340x12
    类似地,两个半字 0x1234 和 0x5678 如下所示:

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


内存偏移量02内存内容0x34120x7856
    下面这个例子解释了 big endian 和 little endian 机器上字节顺序之间的区别。

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

    清单 2. big endian 与 little endian

#include 

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_t、uint32_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 位的值。对于 lon

[1] [2] 下一页

频道声明:本频道的文章除部分特别声明禁止转载的专稿外,可以自由转载.但请务必注明出出处和原始作者 文章版权归本频道与文章作者所有.对于被频道转载文章的个人和网站,我们表示深深的谢意。

原始作者:佚名 录入时间:2007-1-2 3:11:10
信息来源:不详 投稿信箱:itqoo@126.com
教程录入:itqoo    责任编辑:itqoo 
  • 上一个教程:

  • 下一个教程:
  • 【字体: 】【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)
    - 关于我们 - 合作伙伴 - 友情链接 - 广告刊登 - 投稿热线 - 在线留言版权声明联系方式 -
    IT公社版权所有 粤ICP备05127012号
    Copyrigh@2005-2006 itqoo.com.Inc All Rights Reserved  推荐分辨率 1024*768
    联系站长:E-Mail:itqoo@126.com     MSN:urchincc@hotmail.com    QQ:点击这里给我发消息
    特别感谢:亿太网络提供空间支持