zoukankan      html  css  js  c++  java
  • 【转】将32位代码向64位平台移植的注意事项

    原文网址:http://www.51cto.com/art/200604/24942.htm

    新近的64位平台在二进制上与32位应用程序兼容,这意味着可以非常简单地移植现有的程序。许多目前在32位平台上运行良好的程序也许不必移植,除非程序有以下要求:
    ·需要多于4GB的内存。
    ·使用的文件大小常大于2GB。
    ·密集浮点运算,需要利用64位架构的优势。
    ·能从64位平台的优化数学库中受益。
    否则,只需简单地重新编译一下,就已经足够了。大多数编写良好的程序不费吹灰之力就可移植到64位平台之上,在此假定你的程序编写良好,并熟悉本文将要讨论的问题。
    ILP32和LP64数据模型
    32位环境涉及"ILP32"数据模型,是因为C数据类型为32位的int、long、指针。而64位环境使用不同的数据模型,此时的long和指针已为64位,故称作"LP64"数据模型。
    现今所有64位的类Unix平台均使用LP64数据模型,而64位Windows使用LLP64数据模型,除了指针是64位,其他基本类型都没有变。我们在此主要探讨ILP32到LP64的移植问题,表1显示了ILP32与LP64数据模型的差异。
    向 64位移植代码时的所有问题差不多都可以总结出一个简单的规律:千万不要认为int、long、指针的长度一样。任何违反这条规律的代码,当运行在 LP64数据模型下时,都会出现不同的问题,而且很难找出原因所在。例1中有许多违反这条规律的地方,其在移植到64位平台上时都需要重写。
    例1:

    1 int *myfunc(int i)
    2 {
    3  return(&i);
    4 }
    5
    6 int main(void)
    7 {
    8  int myint;
    9  long mylong;
    10 int *myptr;
    11
    12  char *name = (char * ) getlogin();
    13
    14  printf("Enter a number %s: ", name);
    15  (void) scanf("%d", &mylong);
    16  myint = mylong;
    17  myptr = myfunc(mylong);
    18  printf("mylong: %d pointer: %x ", mylong, myptr);
    19  myint = (int)mylong;
    20  exit(0);
    21
    22 }


    第 一步是要求编译器捕捉到移植时的问题,因所用编译器的不同,选项可能也有所不同,但对IBM XL编译器系列,可用的选项有-qwarn64 -qinfo=pro,为了得到64位可执行文件,可使用选项-q64(如果使用GCC,选项应为-m64,表2中列出了其他可用的GCC选项)。图1是 编译例1中代码时的情况。

    将32位代码向64位平台移植的注意事项
    编译例1中代码时的情况

    缺少原型的截断
    如果一个函数被调用时没有指定函数原型,返回值将是32位的int。不使用原型的代码可能会发生意料之外的数据截断,由此导致一个分割错误。编译器捕捉到了例1中第12行的这个错误。
    char *name = (char *) getlogin();
    编译器假定函数返回一个int值,并截短结果指针。这行代码在ILP32数据模型下工作正常,因为此时的int和指针是同样长度,换到LP64模型中,就不一定正确了,甚至于类型转换都不能避免这个错误,因为getlogin()在返回之后已经被截断了。
    要修正这个问题,需包括头文件<unistd.h>,其中有getlogin()的函数原型。
    格式指定符
    如果对64位long、指针使用了32位格式指定符,将导致程序错误。编译器捕捉到了例1中第15行的这个错误。
    (void) scanf("%d", &mylong);
    注意,scanf将向变量mylong中插入一个32位的值,而剩下的4字节就不管了。要修正这个问题,请在scanf中使用%ld指定符。
    第18行也演示了在printf中的一个类似的问题:
    printf("mylong: %d pointer: %x ", mylong, myptr);
    要修正此处的错误,mylong应使用%ld,对myptr使用 %p而不是%x。
    赋值截断
    有关编译器发现赋值截断的一个例子在第16行中:
    myint = mylong;
    这在ILP32模型下不会有任何问题,因为此时的int、long都是32位,而在LP64中,当把mylong赋值给myint时,如果数值大于32位整数的最大值时,数值将被截短。
    被截断的参数
    编译器发现的下一个错误在第17行中,虽然myfunc函数只接受一个int参数,但调用时却用了一个long,参数在传递时会悄无声息地被截断。
    转换截断
    转换截断发生在把long转换成int时,比如说例1中的第19行:

    myint = (int) mylong;


    导致转换截断的原因是int与long非同样长度。这些类型的转换通常在代码中以如下形式出现:

    int length = (int) strlen(str);


    strlen返 回size_t(它在LP64中是unsigned long),当赋值给一个int时,截断是必然发生的。而通常,截断只会在str的长度大于2GB时才会发生,这种情况在程序中一般不会出现。虽然如此, 也应该尽量使用适当的多态类型(如size_t、uintptr_t等等),而不要去管它最下面的基类型是什么。
    一些其他的细小问题
    编译器可捕捉到移植方面的各种问题,但不能总指望编译器为你找出一切错误。
    那些以十六进制或二进制表示的常量,通常都是32位的。例如,无符号32位常量0xFFFFFFFF通常用来测试是否为-1:

    #define INVALID_POINTER_VALUE 0xFFFFFFFF


    然而,在64位系统中,这个值不是-1,而是4294967295;在64位系统中,-1正确的值应为0xFFFFFFFFFFFFFFFF。要避免这个问题,在声明常量时,使用const,并且带上signed或unsigned。

    const signed int INVALID_POINTER_VALUE = 0xFFFFFFFF;


    这行代码将会在32位和64位系统上都运行正常。
    其他有关于对常量硬编码的问题,都是基于对ILP32数据模型的不当认识,如下:

    int **p; p = (int**)malloc(4 * NO_ELEMENTS);


    这行代码假定指针的长度为4字节,而这在LP64中是不正确的,此时是8字节。正确的方法应使用sizeof():

    int **p; p = (int**)malloc( sizeof(*p) * NO_ELEMENTS);


    注意对sizeof()的不正确用法,例如:

    sizeof(int) = = sizeof(int *);


    这在LP64中是错误的。
    符号扩展
    要避免有符号数与无符号数的算术运算。在把int与long数值作对比时,此时产生的数据提升在LP64和ILP32中是有差异的。因为是符号位扩展,所以这个问题很难被发现,只有保证两端的操作数均为signed或均为unsigned,才能从根本上防止此问题的发生。
    例2:

    long k;
    int i = -2;
    unsigned int j = 1;
    k = i + j;
    printf("Answer: %ld ", k);


    你 无法期望例2中的答案是-1,然而,当你在LP64环境中编译此程序时,答案会是4294967295。原因在于表达式(i+j)是一个 unsigned int表达式,但把它赋值给k时,符号位没有被扩展。要解决这个问题,两端的操作数只要均为signed或均为unsigned就可。像如下所示:

    k = i + (int) j

    联合体问题(Union)
    当联合本中混有不同长度的数据类型时,可能会导致问题。如例3是一个常见的开源代码包,可在ILP32却不可在LP64环境下运行。代码假定长度为2的unsigned short数组,占用了与long同样的空间,可这在LP64平台上却不正确。
    例3:

    typedef struct {
     unsigned short bom;
     unsigned short cnt;
     union {
    unsigned long bytes;
    unsigned short len[2];
     } size;
    } _ucheader_t;


    要在LP64上运行,代码中的unsigned long应改为unsigned int。要在所有代码中仔细检查联合体,以确认所有的数据成员在LP64中都为同等长度。
    字节序问题(Endian)
    因 64位平台的差异,在移植32位程序时,可能会失败,原因可归咎于机器上字节序的不同。Intel、IBM PC等CISC芯片使用的是Little-endian,而Apple之类的RISC芯片使用的是Big-endian;小尾字节序(Little- endian)通常会隐藏移植过程中的截断bug。
    例4:

    long k;
    int *ptr;
    int main(void)
    {
     k = 2 ;
     ptr = &k;
     printf("k has the value %ld, value pointed to by ptr is %ld ", k, *ptr);
     return 0;
    }


    例4是一个有此问题的明显例子,一个声明指向int的指针,却不经意间指向了 long。在ILP32上,这段代码打印出2,因为int与long长度一样。但到了LP64上,因为int与long的长度不一,而导致指针被截断。不 管怎么说,在小尾字节序的系统中,代码依旧会给出k的正确答案2,但在大尾字节序(Big-endian)系统中,k的值却是0。

    将32位代码向64位平台移植的注意事项(3)
    找出64位移植问题的可用的GCC鄙夷选项

    表3说明了为什么在不同的字节序系统中,会因截断问题而产生不同的答案。在小尾字节序 中,被截断的高位地址中全为0,所以答案仍为2;而在大尾字节序中,被截断的高位地址中包含值2,这样就导致结果为0,所以在两种情况下,截断都是一种 bug。但要意识到,小尾字节序会隐藏小数值的截断错误,而这个错误只有在移植到大尾字节序系统上时才可能被发现。

    移植到64位平台之后的性能降低
    当代码移植到64位平台之后,也许发现性能实际上降低了。原因与在LP64中的指针长度和数据大小有关,并由此引发的缓存命中率降低、数据结构膨胀、数据对齐等问题。
    由于64位环境中指针所占用的字节更大,致使原来运行良好的32位代码出现不同程度的缓存问题,具体表现为执行效率降低。可使用工具来分析缓存命中率的变化,以确认性能降低是否由此引起。
    在 迁移到LP64之后,数据结构的大小可能会改变,此时程序可能会需要更多的内存和磁盘空间。例如,图2中的结构在ILP32中只需要16字节,但在 LP64中,却需要32字节,整整增长了100%。这缘于此时的long已是64位,编译器为了对齐需要而加入了额外的填充数据。
    通过改变结构中数据排列的先后顺序,能将此问题所带来的影响降到最小,并能减少所需的存储空间。如果把两个32位int值放在一起,会因为少了填充数据,存储空间也随之减少,现在存储整个结构只需要24字节。
    在重排数据结构之前,在根据数据使用的频度仔细衡量,以免因降低缓存命中率而带来性能上的损失。
    如何生成64位代码
    在 一些情况中,32位和64位程序在源代码级别的接口上很难区分。不少头文件中,都是通过一些测试宏来区分它们,不幸的是,这些特定的宏依赖于特定的平台、 特定的编译器或特定的编译器版本。举例来说,GCC 3.4或之后的版本都定义了__LP64__,以便为所有的64位平台通过选项-m64编译产生64位代码。然而,GCC 3.4之前的版本却是特定于平台和操作系统的。
    也许你的编译器使用了不同于 __LP64__的宏,例如IBM XL的编译器当用-q64编译程序时,使用了__64bit__宏,而另一些平台使用_LP64,具体情况可用__WORDSIZE来测试一下。请查看相 关编译器文档,以便找出最适合的宏。例5可适用于多种平台和编译器:
    例5:

    #if defined (__LP64__) || defined (__64BIT__) || defined (_LP64) || (__WORDSIZE == 64)
    printf("I am LP64 ");
    #else
    printf("I am ILP32 ");
    #endif


    共享数据
    在移植到64位平台时的一个典型问题是,如何在32位和64位程序之间读取和共享数据。例如一个32位程序可能把结构体作为二进制文件存储在磁盘上,现在你要在64位代码中读取这些文件,很可能会因LP64环境中结构大小的不同而导致问题。
    对 那些必须同时运行在32位和64位平台上的新程序而言,建议不要使用可能会因LP64和ILP32而改变长度的数据类型(如long),如果实在要用,可 使用头文件<inttypes.h>中的定宽整数,这样不管是通过文件还是网络,都可在32位和64位的二进制层面共享数据。
    例6:

    #include <stdio.h>
    #include <inttypes.h>
    struct on_disk
    {
     /* ILP32|LP64共享时,这个应该使用int32_t */
     long foo;
    };
    int main()
    {
     FILE *file;
     struct on_disk data;
     #ifdef WRITE
    file=fopen("test","w");
    data.foo = 65535;
    fwrite(&data, sizeof(struct on_disk), 1, file);
     #else
    file = fopen("test","r");
    fread(&data, sizeof(struct on_disk), 1, file);
    printf("data: %ld ", data.foo);
     #endif
     fclose(file);
    }


    来 看一下例6,在理想的情况下,这个程序在32位和64位平台上都可正常运行,并且可以读取对方的数据。但实际上却不行,因为long在ILP32和 LP64之中长度会变化。结构on_disk里的变量foo应该声明为int32_t,这个定宽类型可保证在当前ILP32或移植到的LP64数据模型 下,都生成相同大小的数据。
    混合Fortran和C的问题
    许多科学运算程序从C/C++中调用 Fortran的功能,Fortran从它本身来说并不存在移植到64位平台的问题,因为Fortran的数据类型有明确的比特大小。然而,如果混合 Fortran和C语言,问题就来了,如下:例7中C语言程序调用例8中Fortran语言的子例程。
    例7:

    void FOO(long *l);
    main ()
    {
     long l = 5000;
     FOO(&l);
    }


    例8:

    subroutine foo( i )
    integer i
    write(*,*) 'In Fortran'
    write(*,*) i
    return
    end subroutine foo


    例9:

    % gcc -m64 -c cfoo.c
    % /opt/absoft/bin/f90 -m64 cfoo.o foo.f90 -o out
    % ./out
    In Fortran
    0


    当 链接这两个文件后,程序将打印出变量i的值为"5000"。而在LP64中,程序打印出"0",因为在LP64模式下,子例程foo通过地址传递一个 64位的参数,而实际上,Fortran子例程想要的是一个32位的参数。如果要改正这个错误,在声明Fortran子例程变量i时,把它声明为 INTEGER*8,此时和C语言中的long为一样长度。
    结论
    64位平台是解决大型复杂科学及商业问题的希望,大多数编写良好的程序可轻松地移植到新平台上,但要注意ILP32和LP64数据模型的差异,以保证有一个平滑的移植过程。

  • 相关阅读:
    html5中input弹窗时,不弹出输入法弹出。
    ajax异步提交
    WinForm更新文件
    固态硬盘上安装Windows8(ghost)启动问题
    刷新页面Js
    流媒体
    WebOffice上传Word限制设置
    js页面传参中文乱码问题
    weboffice(点聚)在传参为汉字时的乱码问题
    Linq中Lanbda表达式做参数
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4570580.html
Copyright © 2011-2022 走看看