32位与64位编程注意事项总结
http://www.oschina.net/p/flowvisor
http://www.cnblogs.com/centimeter/articles/2395405.html
http://www.360doc.com/content/13/0520/21/7662927_286868466.shtml
http://zhidao.baidu.com/link?url=E8M7y7JbRm8K9gYnxlUW7kLZwpozFT-t4YyTSzW6eb8RKfJKnmszzC17U_6R7-fgfk1IgKxsdY9A8iBXKNYGO_
http://bbs.csdn.net/topics/390426743
http://www.cnblogs.com/xwj-pandababy/articles/3418470.html
http://zhidao.baidu.com/link?url=R7c6hAgB0b8PTKMSLN1JQhWRDHO81pnaS3ZoGSfVuqYQoH8BETssO6p7Xy_87Y7JEyfJeFTZpPjnahgBiXCm3K
http://zhidao.baidu.com/link?url=02zo160Hq9EMTYL3ACVxD2gGdhqmU9JnpN_moQWnWXOHaBagjs4fHzKdzuBVAvCQvOFSs1x6jwCudWs4Yl3qda
http://www.csdn123.com/html/exception/408/408361_408359_408357.htm
http://blog.csdn.net/liuruihan/article/details/24792491
http://blog.csdn.net/david_xtd/article/details/7669028
http://blog.csdn.net/thanklife/article/details/7362893
---------------------------------------------------------------------------------------------
http://www.csdn123.com/html/exception/408/408361_408359_408357.htm
http://blog.csdn.net/liuruihan/article/details/24792491
http://blog.csdn.net/david_xtd/article/details/12652197
http://blog.csdn.net/david_xtd/article/details/18708731
http://blog.csdn.net/david_xtd/article/details/7177041
http://blog.csdn.net/david_xtd/article/details/8682455
http://blog.csdn.net/david_xtd/article/details/9240549
http://blog.csdn.net/thanklife/article/details/7362893
将32位代码向64位平台移植的注意事项
新近的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) |
第一步是要求编译器捕捉到移植时的问题,因所用编译器的不同,选项可能也有所不同,但对IBM XL编译器系列,可用的选项有-qwarn64-qinfo=pro,为了得到64位可执行文件,可使用选项-q64(如果使用GCC,选项应为-m64,表2中列出了其他可用的GCC选项)。图1是编译例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; |
你无法期望例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 { |
要在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; |
例4是一个有此问题的明显例子,一个声明指向int的指针,却不经意间指向了long。在ILP32上,这段代码打印出2,因为int与long长度一样。但到了LP64上,因为int与long的长度不一,而导致指针被截断。不管怎么说,在小尾字节序的系统中,代码依旧会给出k的正确答案2,但在大尾字节序(Big-endian)系统中,k的值却是0。
找出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) |
共享数据
在移植到64位平台时的一个典型问题是,如何在32位和64位程序之间读取和共享数据。例如一个32位程序可能把结构体作为二进制文件存储在磁盘上,现在你要在64位代码中读取这些文件,很可能会因LP64环境中结构大小的不同而导致问题。
对那些必须同时运行在32位和64位平台上的新程序而言,建议不要使用可能会因LP64和ILP32而改变长度的数据类型(如long),如果实在要用,可使用头文件<inttypes.h>中的定宽整数,这样不管是通过文件还是网络,都可在32位和64位的二进制层面共享数据。
例6:
#include <stdio.h> |
来看一下例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); |
例8:
subroutine foo( i ) |
例9:
% gcc -m64 -c cfoo.c |
当链接这两个文件后,程序将打印出变量i的值为"5000"。而在LP64中,程序打印出"0",因为在LP64模式下,子例程foo通过地址传递一个 64位的参数,而实际上,Fortran子例程想要的是一个32位的参数。如果要改正这个错误,在声明Fortran子例程变量i时,把它声明为 INTEGER*8,此时和C语言中的long为一样长度。
结论
64位平台是解决大型复杂科学及商业问题的希望,大多数编写良好的程序可轻松地移植到新平台上,但要注意ILP32和LP64数据模型的差异,以保证有一个平滑的移植过程。
linux源码包,是不是不分64位包和32位包啊?在64位服务器上,可以安装;在32位服务器上也可以安装?
楼主你想累死开发人员啊?
现在的 Linux
下面程序的源代码都是一套,不管是 64还是 32
,也不管是在 PowerPC
上还是在 SUN
的服务器上,还是在手机上还是在你的计算机上,都是只一套源代码。
不过这一套源代码要看是不是真的能用,一些老软件只能字啊 32位系统下面编译,有的新软件可能针对 64位有优化。
跨架构和跨字节数,一般都需要源代码里面有相应的处理的。尤其是大型软件。
Linux 是遵循 POSIX规范的,所以这种一个源代码支持全部的架构理论上是存在的,但实际上各种条件都可能影响源代码的可移植性。
其中的各种原因什么的一句话两句话说不清楚。
对,源码包是不分32位64位的。
如果是RPM包,就是已经编译的那种有分64位和32位,二进制类型的可执行文件一般也有分64和32位,但是源码包没有分,都可以用来编译安装,但是部分源码包64位和32位编译的时候加的参数有点区别而已
什么是64位系统以及64为系统对32位的支持和优缺点
第一讲 什么是64位系统
截至本课程编写的时间为止,市场上有两种受欢迎的64位微处理器体系结构:IA64和Intel64
1. IA-64是由 Intel和HP 合作开发的64位微处理机体系结构。Itanium和Itanium2 微处理机中就是用了这种体系结构。如想了解更多关于IA-64的信息,请查看Itanium。
2. Intel 64 (EM64T / AMD64 / x86-64 / x64)是x86体系的继承,同时保持向后兼容。这种体系结构的名字有不同变型,因而导致了一些疑惑,但是以下这些名字所指的都是同样的事物:x86-64, AA-64,Hammer Architecture, AMD64, Yamhill Technology, EM64T, IA-32e, Intel 64, x64。想了解更多为什么会有这么多不同的名字,请参看文章。
你需要了解到IA-64与Intel64是完全不同、不能相互兼容的微处理机体系结构。在本文的范围内,我们仅讨论在Windows软件开发人员中比较受欢迎的Intel64(x64/AMD64)结构。相对应的,当我们说起Windows操作系统的时候,我们指的对应Intel64体系的64位操作系统。例如,Windows XP Professional x64 Edition,Windows Vista x64, Windows 7 x64。 Intel64所对应的编程模型,对于基于64位windows开发的程序员来说,简称为Win64。
Intel 64 体系结构
以下给出的信息是基于 "AMD64 Architecture Programmer"s Manual. Volume 1.Application Programming".
Intel64 体系结构,在我们看来,就是一个简单但是非常有效的对于现有的商用x86体系结构的反向兼容。Intel64加入了64位地址寻址的内容,同时扩展了资源来更好支持高性能的64位程序。陈旧的16位和32位的应用和操作系统,不需要进行额外的修改或者是重新编译,就可以在64位体系结构上运行。
64位体系结构出现的原因在于应用需要更大的寻址空间。这些应用可能是高性能的服务器,数据管理系统,CAD或者游戏。这些应用将从64位地址空间和更多地寄存器中得到大量的性能提升。在陈旧的x86系统中,只有少量的寄存器存在,因而限制了计算任务的性能。寄存器数量的增加使得能够进一步提高应用的性能。
让我们首先来看一下x64体系的优势。
l 64位寻址空间
l 扩展的寄存器组
l 开发者熟悉的命令集
l 可以在64位结构的操作系统上运行32位程序
l 可以直接使用32位操作系统
64位操作系统
基本上所有现在的操作系统都有支持64位体系结构的版本。例如,Mircosoft就发布了Windows XP x64。大型的UNIX的开发者也发布了64位版,例如Linux Debian 3.5x86-64,但是这不代表着这些系统的全部代码是64位的。因为64位系统提供反向兼容,有些操作系统和许多应用仍然是32位。因此,64位版的Windows使用了一个特殊的模型 WoW64 (Windows-on-Windows64),这个模型能够翻译32位应用的调用来使用64位操作系统的资源。
地址空间
虽然64位处理器理论上能够支持16 Ebytes (2^64)的内存空间,Win64现在仅仅支持16Tbytes (2^44)的空间。造成这个现象的原因有一些:现代的处理器仅仅能够提供1Tbyte (2^40)的物理存储的寻址。这个体系(不是这个硬件部分)可以扩展到支持4 Pbytes (2^52)的空间,但是在这种情况下你需要大量的内存来存储分页表。
除了上述描述的局限以外,每一种64位Windows版本的上内存的大小取决于Mircosoft的商业决定。不同的Windows版本有不同的限制如下表所示。
表1不同Windows版本所支持的地址空间
Win64 编程模型
与Win32类似的,Win64的页面大小也是4 Kbyte。最靠前的64Kbyte地址空间是不开放的,所以最低的正确的地址是0x10000。而不像在Win32中,系统的DLL占据了超过4 Gbyte的空间。
Intel64 的编译器有一个特性:它们可以用寄存器来更有效率的传递参数给函数,而不是使用堆栈。这就使得使用Win64体系的开发人员可以丢弃调用约定(calling convention)的概念。在Win32中,你可以使用许多约定,比如__stdcall, __cdecl, __fastcall。在Win64种,只有一个调用约定。下面的例子,是来描述4个整型参数是怎样通过寄存器的。
l RCX: 第一个参数
l RDX: 第二个参数
l R8: 第三个参数
l R9: 第四个参数
在第四个参数之后的参数通过堆栈来传递。如果是要传递浮点型的参数,需要使用XMM0-XMM3寄存器和堆栈。
在调用约定方面的区别,使得开发人员不能够在同一个程序中同时使用64位和32位的内容。用另一句话来说,如果一个应用是通过64位来编译的,所有的动态链接库也要是64位。
通过寄存器来传递参数,是使得64位程序比32位程序快的一个创新。你可以通过64位的数据结构来取得进一步的性能提升。在下一讲中我们将讨论这方面的问题。
第二讲 64位Windows环境对32位应用的支持
在我们开始讨论64位程序前,让我们谈论一下64位Windows系统对32位应用的反向兼容。反向兼容是通过WoW64中的机制来实现的。
WoW64(Windows-on-Windows 64-bit) 是Windows操作系统的一个子系统,它使得能够在64位Windows系统上执行32位应用。
WoW64子系统不支持以下程序:
1. 为16位操作系统所编译的程序
2. 为32位操作系统所编译的内核(kernel-mode)程序
间接费用
不同处理器体系的WoW64有一些不同。例如,为Intel Itanium 2开发的64位Windows版本,使用WoW64来模拟x86指令。这个模拟比起Intel64的WoW64体系需要更多资源,因为系统在执行32位程序的时候需要从64位模式转换到兼容模式。
Intel 64 (AMD64/ x64) 上的WoW64不需要指令模拟。在这个系统上,WoW64子系统仅仅是通过在32位应用和64位Windows API之间添加额外的一层,来模拟32位的环境。在有些地方,这新加的一层可能比较薄,在另一些地方这一层比较厚。平均来说,对于一个程序,你需要期待因为这一个层次所带来性能上2%的损失。对于有些程序来说,这个数值可能更大。2%可能并不是一个很大的数字,但是你需要铭记在心的是32位程序在64位系统中比它们在32位系统中运行的要慢。
把程序编译为64位的代码,不仅使你避免使用WoW64,同时能使你得到了性能上提升。这个可以通过体系结构上的改变,如更多的通用寄存器来解释。平均来说,对于一个程序,通过简单的重新编译,你可以期待着在性能上有5%-15%的提升。
在64位环境上运行32位程序的好处
因为WoW64,32位程序在64位系统中比它们在32位系统中运行的要慢。但是简单的32位程序可以从在64位系统上执行获得一个好处。或许你知道,如果在32位Windows系统选择“/3gb”,程序编译时选择"/LARGEADDRESSAWARE:YES",它可以分配到最高达3 Gbytes的内存空间。同样的32位程序在64位系统可以分配大最高达4Gbytes的内存空间(现实中大约是3.5 Gbytes的内存空间)。
重定位
WoW64子系统是通过将调用重定位至文件和寄存器,从而将32位程序与64位程序分离开来。这样使得32位程序不会意外的接触64程序的数据。例如,一个从"%systemroot%System32"中调用了DLL文件的32位应用程序,如果不进行隔离,那么它可能调用了一个32位程序无法兼容的64位DLL文件。为了避免这样的情况发生,WoW64位子系统将进入"%systemroot%System32"文件夹的调用重定位到调用"%systemroot%SysWOW64"文件夹中的文件。这种重定位使得开发者能避免兼容性的问题,因为32位应用程序需要能与它们兼容的特殊的DLL文件。
如想了解更多关于文件系统或寄存器的重定位,可以参考MSDN相关章节"Running32-bit Applications"。
为什么32位DLL不能在64位程序中使用? 是否有方式可以避免这种局限?
现在是不能够在64位进程中调用32位DLL并执行它的代码。这种不可行是因为64位系统的设计造成的。所以说是从根本上的不可能。没有任何技术,也没有任何文档没有记录的方式可以帮助到你。为了做到这个,你必须要装载并初始化WoW64,更不用说内核结构的不同了。这代表着64位的处理器必须在运行中被处理为32位。这个话题在"Why can"t you thunk between 32-bit and 64-bitWindows?". 中有更为详尽的描述。有一件事情是我建议可以尝试的,就是建立一个使用COM技术创建一个替代程序,你可以阅读"Accessing32-bit DLLs from 64-bit code".
但是,从32位DLL将资源导入到64位进程中去,是比较容易的,你所需要的就是在调用LoadLibraryEx的时候制定以下的flag:LOAD_LIBRARY_AS_DATAFILE。
逐渐放弃对32位程序的支持
微软公司为了进一步促进程序向64位迁移,会逐渐在一些版本的Windows操作系统中取消对32位程序的支持。这是一个自然的过程。当然这个过程会比较缓慢,但这个行为已经开始了。
许多管理员可能会知道一个较新的操作系统上的服务器安装和操作模式,叫做Server Core。这个模块正是在持久的“Windows VS Linux”大战中被广泛提起。其中一个支持使用Linux的原因就是Linux支持不需要图像接口就可以安装服务器操作。但是,现在Windows Server
也有了这种能力。如果你在这种模式中安装系统,你将只获得系统的命令行,而不需要用户界面。
这种能力(Server Core安装)出现在Windows Server 2008.但是在 Windows Server2008 R2中,另外一种创新出现使得系统更接近64位。在Windows Server 2008 R2 (Server Core )中,你可以启动或者禁用系统对32位应用程序的支持。更重要的是对32位应用程序的支持默认是被禁用的。所以当你尝试在Server Core mode中启动32位程序的时候,你会得到一条系统消息告诉你,这是不可以的。如果你需要额外手动启用这种支持,可以通过以下命令实现:
start /w ocsetupServerCore-WOW64
在普通的模式(完全安装),执行32位程序的支持默认是启用的。
这种趋势是十分明显的,因而现在开始创建64位版本的应用是理智的,因为64位的程序能保证在更多的操作系统版本上使用。
额外信息
Alexey Pahunov的 Russian blog 也是获取WoW64资料的好地方。Alexey是Microsoft的员工,他参与了WoW64子系统的开发。
第三讲 将代码导入64位系统的优缺点
你需要带着以下的问题来学习64位系统:“往64位系统重新编译项目的合理性有多少?”回答这个问题,你需要花费一些时间和精力来思考。在一方面,你可能会因为要提供64位系统的应用支持,使得你在市场上落后于你的对手。在另一方面,你可能会花费了时间在开发64位系统应用上,而这一努力并不能给你带来竞争优势。
以下是一些建议可以用来帮助你做出选择。
应用的生命期
当你的应用有一个比较短的生命期,你暂时还没有必要开发应用的64位版本。WoW64子系统使得陈旧的32位应用在64位机上也能取得比较好的表现。如果你在两年内会停止运营你的产品,你是不需要现在建立一个64位版本的。实践证明向64位系统的迁移是一个非常缓慢平和的过程。可能你的大多数用户在未来的一段时间里还是会仅仅使用你系统的32位版本。你需要知道,本教程是在2009年编写的,这时候大部分用户都还是在使用32位系统。但是很快32位程序,就会变得越来越不自然,会变得落伍的。
如果你的项目有一比较长的开发和维护时间,你需要开始建立你产品的64位版本。当然你可以慢慢来,但是你要记住的是,越迟拥有产品的64位版本,在64位系统上维护32位版本所带来的问题将越来越多。
程序性能要求
当一个程序重新编译以适应64位版本后,程序可以应用大量的内存资源,它的速度将提高5%-15%。5%-10%的系统提升是因为64位体系结构的特点,如更多的寄存器,所导致的。另外的1%-5%的性能提升是因为抛弃了翻译32位应用使之在64位系统环境运行的WoW64层所带来。
例如,Adobe公司称新版本64位的"Photoshop CS4"比32位版本的快了12%。
需要大量的内存的应用可以期待着有更高的性能提升。比如说,图像编辑器,CAD系统,GSI CAD,数据库和其他的模型包。能将所有的数据存储在内存中,而避免了多余的从硬盘中导入的工作,使得这些应用的速度,可能不是提升了几个百分点,而是成倍的提高。
例如,Alfa-Bank曾经在他们的IT 基础设施中使用了基于Itanium 2的平台。原因在于他们业务的发展使得现有系统无法支持不断增长的数据量。每个用户的服务延迟变得非常严重。分析显示,系统的瓶颈不在于处理器的表现,而在于32位体系结构与内存子系统的关系上,因为32位体系结构使得只能使用4 Gbyte的服务器地址空间。而它们的数据库却大于9 Gbyte。因而导致了子系统的输入输出的临界负荷。Alfa-Bank决定购买一个由2个服务器组成的集群。这两个服务器都拥有4处理器、是基于Itanium 2的、拥有12Gbyte内存的。这个决定使得他们的性能要求达到了可以容忍的程度。这个公司的代表称,引入基于Itanium2的服务器使得他们解决了重要问题,同时也节约了花费。
在项目中使用第三方库
在决定是否开发你产品的64位版本前,请先确认你所依赖的第三方库是否有64位版本。你需要找出第三方软件64位版本的定价是多少。这些你都可以在库开发者的网站上找到。如果,没有依赖库的64位版本的支持,可以寻找其他支持64位的库作为替代。
你所开发的库的第三方开发者
如果你在开发库,组件,或者其他需要给第三方开发者使用的程序时,你需要尽快的开发一个你的库的64位版本。否则需要64位支持的你的客户会转向其它的替代产品。例如,一些软件和硬件安全方面的开发者,都非常迟的开发64位版本,使得他们的一些客户转向了其他工具来保护自己的产品。
发布你所开发库的64位版本有另一个好处,你可以把它作为一个单独模块来出售。因而想要开发32位和64位的客户会要购买2个不同的授权。例如Spatial Corporation,就是按这个方式来卖他们的Spatial ACIS库的。
16位应用
如果你的应用仍然有16位的模块,你需要丢弃它们。64位的Windows版本完全不支持16位的应用。
关于使用16位安装程序的人员,我需要解释一个事情。这样的安装程序仍然在被使用于安装一些32位应用程序。因为对于一些比较流行的16位的安装程序,它们之中包含了一些机制,使得它们能够在运行中被更新的32位安装程序。这可能使得你觉得16位的程序,在64位的系统环境中仍能使用,但这是错误的,请牢记。
汇编器代码
不要忘记了,大量的汇编器代码片是使得创建应用的64位版本更为困难的原因之一。
工具箱
如果考虑以上提到各种因素后,你决定创建一个你的应用的64位版本,成功不是一定的。你还需要一些必要的工具,以下就是一些你可能会碰到的不愉快的事情。
首先最令人不开心的事情,就是缺少64位的编译器。当我们写本教程的时候(2009),Embarcadero还没有64位C++编译器,它有期望在2009年年底发布一个。除非你重新编写你自己的部署部分,如使用Microsoft Visual Studio时可以快速改变配置,否者你没有办法回避这个问题。但其他的问题,就没有像缺少编译器这样,容易被察觉,可能会在你将程序导入一个新的体系结构的时候发生。你可以在开始工作前,做一个调研,看所有需要的组件是否都有64位版本可以使用。不然,你将面临不愉快的事情。
做决定时,还需要注意到一个我们还没提到的问题,修改你的代码使之可以在64位模式下编译所需要的花销。我们会在之后的课程里,告诉你怎样估计这个花销。这个花销可能是非常高昂的,需要经过系统的计划和详细的时间安排。
第五讲 编译64位应用
我们需要向读者说明,我们不可能涵盖编译一个64位应用的所有细节问题。每个项目有自己的特有的设定,所以你需要特别注意这些设定。本讲只会涵盖对任何程序都重要的步骤,这些步骤只是一个开始,更多的问题需要读者自己去解决。
库
当你尝试编译一个你产品的64位版本时,首先确认所有必须的64位的库已经成功安装了,而且它们的路径是正确的。例如32位与64位的以“lib”结尾的库文件是不同的,而且在不同的目录中,如果有bug请修改。
注意:如果库是以源代码的形式存在的,那么会存在库项目的64位的配置。注意到,当你修改一个源代码的配置,以编译适合你的64位版本时,你存在可能侵犯了版权的可能性,请谨慎。
汇编器
Visual C++ 不支持64位的内联汇编(inline assembler),你可以选择一个外部的64位汇编器(如,MASM)或者是重写这些汇编语言。
编译错误和警告的实例
当开始编译你的程序时,你会碰到许多因为显式类型转换和隐式类型转换所带来的问题(explicit and implicit type conversions)。以下是一个例子。
这段代码在32位上成功编译,但在64位上不行,Visual C++会产生如下warning
因为函数strlen()返回的类型是size_t,在32位系统上,类型size_t与unsigned int的类型一致,所以编译器选择"voidfoo(unsigned int)"来调用。但在64位的模式下,size_t与unsigned int的类型不一致。 size_t变成了64位,但unsigned int类型仍然保持了32位。因而编译器不知道哪一个foo()函数来调用。
现在来考虑一段由Visual C++编译64位代码时,所产生的错误:
GetSize()函数返回的类型是 INT_PTR,在32位时,与int类型一致。在64位时,INT_PTR是64位而隐式的转换为32位。因为可能会导致高位值丢失,这就是系统给出警告的原因。当数组中元素数量超过了INT_MAX,一个隐式类型转换就有可能会导致错误。去除这个警告和可能错误的方式就是,你应该将len的类型写为INT_PTR或者是ptrdiff_t 类型。
在你已经完全了解了64位错误的规律前,都不要去隐藏warning。你有可能会隐藏一个错误,使得之后更难发现它。你可以在之后的课程中,学到更多的关于64位错误的规律和发现他们的方式。你可以同样参考一下文章 "20issues of porting C++ code on the 64-bit platform", "A 64-bit horse that can count"。
size_t 和ptrdiff_t 类型
在大多数关于数据不兼容的编译错误和警告中,我们应该尤其考虑两种类型 size_t和ptrdiff_t,这两者在编译64位代码中最容易出现问题。如果你使用的是Visual C++编译器,这些类型可能被整合到编译器中,你不需要增加库描述文件。但是如果你使用的是GCC,你可能需要添加头文件“stddef.h”。
size_t 是一个C/C++基本unsigned integer类型。它是sizeof操作的返回值。这种类型的大小之所以这样选择,是因为它可以用来存储理论上的最大数组的大度值。例如,size_t在32位系统上是32位,在64位系统上是64位。换句话说,你可以安全的用size_t类型的变量存储一个指针,但是不包括指向函数的指针。这个类型经常作为在循环中的计数器的类型,作为数组索引,用来存储长度,或者用于地址的计算中。以下的这些类型与size_t类似:SIZE_T, DWORD_PTR, WPARAM, ULONG_PTR。虽然你可以在size_t中存储一个指针,但是最好使用另外一个unsigned integer类型uintptr_t ——它的名字就反映了它的用途。size_t与uintptr_t是同义的。
ptrdiff_t是一个C/C++基本signed integer类型。这种类型的大小之所以这样选择,是因为它可以用来存储理论上的最大数组的大度值。例如,ptrdiff _t在32位系统上是32位,在64位系统上是64位。与size_t类似,你可以安全的用ptrdiff _t 类型的变量存储一个指针,但是不包括指向函数的指针。ptrdiff_t类型也是表达式"ptr1-ptr2"的返回值。这个类型经常作为在循环中的计数器的类型,作为数组索引,用来存储长度,或者用于地址的计算中。与它类似的是SSIZE_T, LPARAM,INT_PTR, LONG_PTR。与它同义的是intptr_t,intptr_t可以用来存储一个指针。
size_t 和ptrdiff_t被创造就是为保证地址运算的正确性。在较长的时间内,int的类型都与机器字(machine word)的长度一致,因而int类型变量经常被作为数组索引,或者是用来存储物体和指针大小。因而地址运算也是通过int和unsigned类型来编译的。int类型在大多数的C/C++教程中出现在循环体做作为索引。下面是一个经典的例子:
当处理器不断的发展,继续扩展int的长度显得不那么合理了。这是由许多因素造成的如:节约使用的内存,最大的兼容性等。因而,许多的数据模式出现,来描述基本C与C++类型之间的关系。所以现在选择一个类型来存储指针和物体大小就不那么容易了。size_t和ptrdiff_t类型的出现就成了最好的解决方案。它们肯定可以在地址运算中使用。现在如下的代码将成为经典:
这段代码才是能提供最好的安全性,移植性和高性能的代码。在之后的课程中你将学到原因。
size_t 和ptrdiff_t类型可以被称为memsize类型。Memsize的出现是为了简单的描述所有能作为存储最大数组的长度值,和作为这样数组的索引的值的类型。当说到memsize类型时,你需要能立刻想到它们在32位系统上是32位的,在64位系统上是64位的。以下是一些memsize类型:size_t, ptrdiff_t,pointers, SIZE_T, LPARAM。
32移位植到64位 注意事项
32位移植到64位 注意事项
32bit-64bit porting work注意事项
64位服务器逐步普及,各条产品线对64位升级的需求也不断加大。在本文中,主要讨论向64位平台移植现有32位代码时,应注意的一些细小问题。
什么样的程序需要升级到64位?
理论上说,64位的操作系统,对32位的程序具有良好的兼容性,即使全部换成64位平台,依然可以良好的运行32位的程序。因此,许多目前在32位平台上运行良好的程序也许不必移植,有选择,有甄别的进行模块的升级,对我们工作的展开,是有帮助的。
什么样的程序需要升级到64位呢?
除非程序有以下要求:
l 需要多于4GB的内存。
l 使用的文件大小常大于2GB。
l 密集浮点运算,需要利用64位架构的优势。
l 能从64位平台的优化数学库中受益。
ILP32和LP64数据模型
32位环境涉及"ILP32"数据模型,是因为C数据类型为32位的int、long、指针。而64位环境使用不同的数据模型,此时的long和指针已为64位,故称作"LP64"数据模型。下面的表中列出了常见的类型大小比较:
Data type |
Data length(32bit) |
Data length(64bit) |
Signed |
char |
8 |
8 |
Y |
unsigned char |
8 |
8 |
N |
short |
16 |
16 |
Y |
unsigned short |
16 |
16 |
N |
int |
32 |
32 |
Y |
unsigned int |
32 |
32 |
N |
long |
32 |
64 |
Y |
unsigned long |
32 |
64 |
N |
long long |
64 |
64 |
Y |
point |
32 |
64 |
N |
size_t |
32 |
64 |
N |
ssize_t |
32 |
64 |
Y |
off_t |
32 |
64 |
Y |
|
|
|
|
由上表我们可以看出,32位到64位的porting工作,主要就是处理长度变化所引发的各种问题。在32位平台上很多正确的操作,在64位平台上都不再成立。例如:long->int等,会出现截断问题等。下面将详细阐述具体遇到的问题,并给出修改策略。
截断问题
截断问题是在32-64porting工作中最容易遇到的问题。
部分的截断问题能够被编译器捕捉到,采用-Wall –W进行编译,永远没有坏处。这种问题处理方法也非常简单,举个例子来说:
long mylong;
(void)scanf("%d",&mylong);// warning: int format, different type arg(arg 2)
long mylong;
(void)scanf("%ld",&mylong);// ok
但有很多情况下,一些截断性问题并不能被良好的诊断出来。
例如:
long a;
int b;
b = a;
在这种情况下,编译器会直接进行转换(截断处理),编译阶段不报任何警告。当a的数据范围在2G范围内时,不会出问题,但是超出范围,数据将出现问题。
另外,采用了强制转换的方式,使一些隐患被保留了下来,例如:
long mylong;
(void)scanf("%d",(int*)&mylong);//编译成功,但mylong的高位未被赋值,有可能导致问题。
采用pclint可以有效的检查这种问题,但是,在繁多的warning 中,找到需要的warning,并不是一件容易的事情。
因此,在做平台移植的时候,对于截断问题,最根本的还是逐行阅读代码,详细检测。
在编码设计的时候,尽量保持使用变量类型的一致性,避免发生截断问题。
建议:在接口以及数据结构的定义中不要使用指针,long,以及用long定义的类型(size_t, ssize_t, off_t, time_t),由于字长的变化,这些类型不能32/64位兼容。
一个讨厌的类型size_t:在32bit平台上,它的原形是unsigned int,而在64bit平台上,它的原形式unsigned long。这导致在printf等使用时:无论使用%u或者%lu都会有一个平台报warning。目前我们的解决办法是:采用%lu打印,并且size_t强制转换为unsingedlong。在小尾字节序(Little-endian)的系统中,这种转换是安全的。
常量有效性问题
那些以十六进制或二进制表示的常量,通常都是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位系统上都运行正常。或者,根据需要适当地使用 “L” 或 “U” 来声明整型常量。
又比如对最高位的设置,通常我们的做法是定义如下的常量0x80000000,但是可移植性更好的方法是使用一个位移表达式:1L <<((sizeof(long) * 8) - 1);
参数问题
在参数的数据类型是由函数原型定义的情况中,参数应该根据标准规则转换成这种类型
。在参数类型没有指定的情况中,参数会被转换成更大的类型
。在 64 位系统上,整型被转换成 64 位的整型值,单精度的浮点类型被转换成双精度的浮点类型
。如果返回值没有指定,那么函数的缺省返回值是 int 类型的
。避免将有符号整型和无符号整型的和作为 long 类型传递(见符号扩展问题)
请看下面的例子:
long function (long l);
int main () {
int i = -2;
unsigned k = 1U;
long n = function (i + k);
}
上面这段代码在 64 位系统上会失败,因为表达式 (i + k) 是一个无符号的 32 位表达式,在将其转换成long 类型时,符号并没有得到扩展。解决方案是将一个操作数强制转换成 64 位的类型。
扩充问题(指针范围越界)
扩充问题与代码截断问题刚好相反。请看下面的例子:
int baidu_gunzip(char*inbuf,int len,char* outbuf,int* size)
{
……
ret=bd_uncompress((Byte*)outbuf,(uLongf*)size,
(Byte*)(inbuf+beginpos),inlen);
}
这是ullib库中baidugz模块的一段代码。在这段代码中,将int型的指针改为long型的指针传递给了bd_uncompress函数。在32位系统中,由于int与long都是32bit,程序没有任何问题,但在64位系统中,将导致指针控制的范围在调用函数中扩展为原来的2倍,这将有可能导致程序出core,或指示的值不正确。这种问题比较隐蔽,很难发现,但危害极大,需要严格注意。
解决方法:加强对指针型参数的检查,看是否有范围扩充的问题。
符号扩展问题
要避免有符号数与无符号数的算术运算。在把int与long数值作对比时,此时产生的数据提升在LP64和ILP32中是有差异的。因为是符号位扩展,所以这个问题很难被发现,只有保证两端的操作数均为signed或均为unsigned,才能从根本上防止此问题的发生。
例如:
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
在 C/C++ 中,表达式是基于结合律、操作符的优先级和一组数学计算规则的。要想让表达式在 32 位和 64 位系统上都可以正确工作,请注意以下规则:
l 两个有符号整数相加的结果是一个有符号整数。
l int 和 long 类型的两个数相加,结果是一个long 类型的数。
l 如果一个操作数是无符号整数,另外一个操作数是有符号整数,那么表达式的结果就是无符号整数。
l int 和 doubule 类型的两个数相加,结果是一个 double 类型的数。此处 int 类型的数在执行加法运算之前转换成 double 类型。
将字符指针和字符字节声明为无符号类型的,这样可以防止 8 位字符的符号扩展问题。
联合体问题(Union)
当联合本中混有不同长度的数据类型时,如果单独使用里面定义的成员,一般没有问题。但在一些复杂的操作中,例如几种类型的混用,可能会导致问题。如例3是一个常见的开源代码包,可在ILP32却不可在LP64环境下运行。代码假定长度为2的unsigned short数组,占用了与long同样的空间,可这在LP64平台上却不正确。
union{
unsigned long bytes;
unsigned short len[2];
} size;
正确的方法是检查是否对结构体有特殊的应用,如果有,那么需要在所有代码中仔细检查联合体,以确认所有的数据成员在LP64中都为同等长度。
对齐问题
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32 位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。
g++默认对齐方式是按结构中相临数据类型最长的进行
在32位上,这些类型默认使用一个机器字(4字节)对齐。
在64位上,这些类型默认使用最大两个机器字(8×2=16字节)对齐(按16字节对齐会有好处么?估计于编译器的具体实现相关)
举两个例子说明一下:
struct asdf {
int a;
long long b;
};
这个结构在32bit操作系统中,sizeof(asdf) =12,在64bit操作系统中,sizeof(asdf)=16
struct asdf {
int a;
long double b;
};
这个结构在32bit操作系统中,sizeof(asdf) =16,在64bit操作系统中,sizeof(asdf)=32.这里需要说明的是,32位sizeof(long dou