ie浏览器64位改32位的游戏如何用编程改成32位

62添加评论分享收藏感谢收起赞同 1添加评论分享收藏感谢收起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 int *myfunc(int i)
3  return(&i);
6 int main(void)
12  char *name = (char * ) getlogin();
14  printf(&Enter a number %s: &, name);
15  (void) scanf(&%d&, &mylong);
16  myint =
17  myptr = myfunc(mylong);
18  printf(&mylong: %d pointer: %x \n&, mylong, myptr);
19  myint = (int)
20  exit(0);
第一步是要求编译器捕捉到移植时的问题,因所用编译器的不同,选项可能也有所不同,但对IBM XL编译器系列,可用的选项有-qwarn64-qinfo=pro,为了得到64位可执行文件,可使用选项-q64(如果使用GCC,选项应为-m64,表2中列出了其他可用的GCC选项)。图1是编译例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 \n&, mylong, myptr);
要修正此处的错误,mylong应使用%ld,对myptr使用 %p而不是%x。
赋值截断
有关编译器发现赋值截断的一个例子在第16行中:
这在ILP32模型下不会有任何问题,因为此时的int、long都是32位,而在LP64中,当把mylong赋值给myint时,如果数值大于32位整数的最大值时,数值将被截短。
被截断的参数
编译器发现的下一个错误在第17行中,虽然myfunc函数只接受一个int参数,但调用时却用了一个long,参数在传递时会悄无声息地被截断。
转换截断发生在把long转换成int时,比如说例1中的第19行:
myint = (int)
导致转换截断的原因是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,而是;在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,才能从根本上防止此问题的发生。
int i = -2;
unsigned int j = 1;
k = i +
printf(&Answer: %ld\n&, k);
你无法期望例2中的答案是-1,然而,当你在LP64环境中编译此程序时,答案会是。原因在于表达式(i+j)是一个 unsigned int表达式,但把它赋值给k时,符号位没有被扩展。要解决这个问题,两端的操作数只要均为signed或均为unsigned就可。像如下所示:
k = i + (int) j
联合体问题(Union)
当联合本中混有不同长度的数据类型时,可能会导致问题。如例3是一个常见的开源代码包,可在ILP32却不可在LP64环境下运行。代码假定长度为2的unsigned short数组,占用了与long同样的空间,可这在LP64平台上却不正确。
typedef struct {
unsigned short len[2];
} _ucheader_t;
要在LP64上运行,代码中的unsigned long应改为unsigned int。要在所有代码中仔细检查联合体,以确认所有的数据成员在LP64中都为同等长度。
字节序问题(Endian)
因64位平台的差异,在移植32位程序时,可能会失败,原因可归咎于机器上字节序的不同。Intel、IBM PC等CISC芯片使用的是Little-endian,而Apple之类的RISC芯片使用的是Big-endian;小尾字节序(Little- endian)通常会隐藏移植过程中的截断bug。
int main(void)
 ptr = &k;
 printf(&k has the value %ld, value pointed to by ptr is %ld\n&, k, *ptr);
 return 0;
例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可适用于多种平台和编译器:
#if defined (__LP64__) || defined (__64BIT__) || defined (_LP64) || (__WORDSIZE == 64)
printf(&I am LP64\n&);
printf(&I am ILP32 \n&);
在移植到64位平台时的一个典型问题是,如何在32位和64位程序之间读取和共享数据。例如一个32位程序可能把结构体作为二进制文件存储在磁盘上,现在你要在64位代码中读取这些文件,很可能会因LP64环境中结构大小的不同而导致问题。
对那些必须同时运行在32位和64位平台上的新程序而言,建议不要使用可能会因LP64和ILP32而改变长度的数据类型(如long),如果实在要用,可使用头文件&inttypes.h&中的定宽整数,这样不管是通过文件还是网络,都可在32位和64位的二进制层面共享数据。
#include &stdio.h&
#include &inttypes.h&
struct on_disk
 /* ILP32|LP64共享时,这个应该使用int32_t */
int main()
 struct on_
 #ifdef WRITE
file=fopen(&test&,&w&);
data.foo = 65535;
fwrite(&data, sizeof(struct on_disk), 1, file);
file = fopen(&test&,&r&);
fread(&data, sizeof(struct on_disk), 1, file);
printf(&data: %ld\n&, data.foo);
 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语言的子例程。
void FOO(long *l);
 long l = 5000;
 FOO(&l);
subroutine foo( i )
write(*,*) 'In Fortran'
write(*,*) i
end subroutine foo
% gcc -m64 -c cfoo.c
% /opt/absoft/bin/f90 -m64 cfoo.o foo.f90 -o out
In Fortran
当链接这两个文件后,程序将打印出变量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
,也不管是在 PowerPC
上还是在 SUN 的服务器上,还是在手机上还是在你的计算机上,都是只一套源代码。
不过这一套源代码要看是不是真的能用,一些老软件只能字啊 32
位系统下面编译,有的新软件可能针对 64
位有优化。
跨架构和跨字节数,一般都需要源代码里面有相应的处理的。尤其是大型软件。
Linux 是遵循 POSIX
规范的,所以这种一个源代码支持全部的架构理论上是存在的,但实际上各种条件都可能影响源代码的。
其中的各种原因什么的一句话两句话说不清楚。
对,源码包是不分32位64位的。
如果是RPM包,就是已经编译的那种有分64位和32位,二进制类型的可执行文件一般也有分64和32位,但是源码包没有分,都可以用来编译安装,但是部分源码包64位和32位编译的时候加的参数有点区别而已
第一讲&&什么是<span style="color:#位系统
截至本课程编写的时间为止,市场上有两种受欢迎的<span style="color:#位微处理器体系结构:IA64
<span style="color:#.&&&IA-64是由 Intel
合作开发的<span style="color:#位微处理机体系结构。Itanium
和Itanium2
微处理机中就是用了这种体系结构。如想了解更多关于IA-64的信息,请查看。
<span style="color:#.&&&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体系的<span style="color:#位操作系统。例如,Windows XP Professional
x64 Edition,Windows Vista x64, Windows 7 x64。 Intel64所对应的编程模型,对于基于<span style="color:#位windows开发的程序员来说,简称为。
Intel 64&体系结构
以下给出的信息是基于&&&.
Intel64 体系结构,在我们看来,就是一个简单但是非常有效的对于现有的商用x86体系结构的反向兼容。Intel64
加入了<span style="color:#位地址寻址的内容,同时扩展了资源来更好支持高性能的<span style="color:#位程序。陈旧的<span style="color:#位和<span style="color:#位的应用和操作系统,不需要进行额外的修改或者是重新编译,就可以在<span style="color:#位体系结构上运行。
<span style="color:#位体系结构出现的原因在于应用需要更大的寻址空间。这些应用可能是高性能的服务器,数据管理系统,CAD或者游戏。这些应用将从<span style="color:#位地址空间和更多地寄存器中得到大量的性能提升。在陈旧的x86系统中,只有少量的寄存器存在,因而限制了计算任务的性能。寄存器数量的增加使得能够进一步提高应用的性能。
让我们首先来看一下x64体系的优势。
l& 64位寻址空间
l& 扩展的寄存器组
l& 开发者熟悉的命令集
l& 可以在<span style="color:#位结构的操作系统上运行<span style="color:#位程序
l& 可以直接使用<span style="color:#位操作系统
<span style="color:#位操作系统
基本上所有现在的操作系统都有支持<span style="color:#位体系结构的版本。例如,Mircosoft就发布了Windows
XP x64。大型的UNIX的开发者也发布了<span style="color:#位版,例如Linux Debian 3.5x86-64,但是这不代表着这些系统的全部代码是<span style="color:#位的。因为<span style="color:#位系统提供反向兼容,有些操作系统和许多应用仍然是<span style="color:#位。因此,<span style="color:#位版的Windows
使用了一个特殊的模型&&(Windows-on-Windows64),这个模型能够翻译<span style="color:#位应用的调用来使用<span style="color:#位操作系统的资源。
虽然<span style="color:#位处理器理论上能够支持<span style="color:# Ebytes (2^64)的内存空间,Win64现在仅仅支持<span style="color:#Tbytes
(2^44)的空间。造成这个现象的原因有一些:现代的处理器仅仅能够提供<span style="color:#Tbyte (2^40)的物理存储的寻址。这个体系(不是这个硬件部分)可以扩展到支持<span style="color:# Pbytes (2^52)的空间,但是在这种情况下你需要大量的内存来存储分页表。
除了上述描述的局限以外,每一种<span style="color:#位Windows版本的上内存的大小取决于Mircosoft的商业决定。不同的Windows版本有不同的限制如下表所示。
& && & & & & & & & & &
&&&&&&&&&&&&&表<span style="color:#
不同Windows版本所支持的地址空间
Win64&编程模型
与Win32类&#20284;的,Win64的页面大小也是<span style="color:# Kbyte。最靠前的<span style="color:#Kbyte
地址空间是不开放的,所以最低的正确的地址是<span style="color:#x10000。而不像在Win32中,系统的DLL占据了超过<span style="color:#
Gbyte的空间。
Intel64 的编译器有一个特性:它们可以用寄存器来更有效率的传递参数给函数,而不是使用堆栈。这就使得使用Win64体系的开发人员可以丢弃调用约定(calling
convention)的概念。在Win32中,你可以使用许多约定,比如__stdcall, __cdecl, __fastcall。在Win64种,只有一个调用约定。下面的例子,是来描述<span style="color:#个整型参数是怎样通过寄存器的。
l& RCX: 第一个参数
l& RDX: 第二个参数
l& R8: 第三个参数
l& R9: 第四个参数
在第四个参数之后的参数通过堆栈来传递。如果是要传递浮点型的参数,需要使用XMM0-XMM3
寄存器和堆栈。
在调用约定方面的区别,使得开发人员不能够在同一个程序中同时使用<span style="color:#位和<span style="color:#位的内容。用另一句话来说,如果一个应用是通过<span style="color:#位来编译的,所有的动态链接库也要是<span style="color:#位。
通过寄存器来传递参数,是使得<span style="color:#位程序比<span style="color:#位程序快的一个创新。你可以通过<span style="color:#位的数据结构来取得进一步的性能提升。在下一讲中我们将讨论这方面的问题。
第二讲 64位Windows环境对<span style="color:#位应用的支持
在我们开始讨论<span style="color:#位程序前,让我们谈论一下<span style="color:#位Windows系统对<span style="color:#位应用的反向兼容。反向兼容是通过WoW64中的机制来实现的。
WoW64(Windows-on-Windows 64-bit)
是Windows操作系统的一个子系统,它使得能够在<span style="color:#位Windows系统上执行<span style="color:#位应用。
WoW64子系统不支持以下程序:
<span style="color:#.&&&为<span style="color:#位操作系统所编译的程序
<span style="color:#.&&&为<span style="color:#位操作系统所编译的内核(kernel-mode)程序
不同处理器体系的WoW64
有一些不同。例如,为Intel Itanium 2
开发的<span style="color:#位Windows版本,使用WoW64来模拟x86
指令。这个模拟比起Intel64
的WoW64体系需要更多资源,因为系统在执行<span style="color:#位程序的时候需要从<span style="color:#位模式转换到兼容模式。
Intel 64 (AMD64/ x64) 上的WoW64
不需要指令模拟。在这个系统上,WoW64子系统仅仅是通过在<span style="color:#位应用和<span style="color:#位Windows
API之间添加额外的一层,来模拟<span style="color:#位的环境。在有些地方,这新加的一层可能比较薄,在另一些地方这一层比较厚。平均来说,对于一个程序,你需要期待因为这一个层次所带来性能上<span style="color:#%的损失。对于有些程序来说,这个数&#20540;可能更大。<span style="color:#%可能并不是一个很大的数字,但是你需要铭记在心的是<span style="color:#位程序在<span style="color:#位系统中比它们在<span style="color:#位系统中运行的要慢。
把程序编译为<span style="color:#位的代码,不仅使你避免使用WoW64,同时能使你得到了性能上提升。这个可以通过体系结构上的改变,如更多的通用寄存器来解释。平均来说,对于一个程序,通过简单的重新编译,你可以期待着在性能上有<span style="color:#%-15%的提升。
在<span style="color:#位环境上运行<span style="color:#位程序的好处
因为WoW64,<span style="color:#位程序在<span style="color:#位系统中比它们在<span style="color:#位系统中运行的要慢。但是简单的<span style="color:#位程序可以从在<span style="color:#位系统上执行获得一个好处。或许你知道,如果在<span style="color:#位Windows系统选择“/3gb”,
程序编译时选择&/LARGEADDRESSAWARE:YES&,它可以分配到最高达<span style="color:# Gbytes的内存空间。同样的<span style="color:#位程序在<span style="color:#位系统可以分配大最高达<span style="color:#Gbytes的内存空间(现实中大约是<span style="color:#.5
Gbytes的内存空间)。
WoW64子系统是通过将调用重定位至文件和寄存器,从而将<span style="color:#位程序与<span style="color:#位程序分离开来。这样使得<span style="color:#位程序不会意外的接触<span style="color:#程序的数据。例如,一个从&%systemroot%\System32&中调用了DLL文件的<span style="color:#位应用程序,如果不进行隔离,那么它可能调用了一个<span style="color:#位程序无法兼容的<span style="color:#位DLL文件。为了避免这样的情况发生,WoW64位子系统将进入&%systemroot%\System32&文件夹的调用重定位到调用&%systemroot%\SysWOW64&文件夹中的文件。这种重定位使得开发者能避免兼容性的问题,因为<span style="color:#位应用程序需要能与它们兼容的特殊的DLL文件。
如想了解更多关于文件系统或寄存器的重定位,可以参考MSDN相关章节&&。
为什么<span style="color:#位DLL不能在<span style="color:#位程序中使用?&是否有方式可以避免这种局限?
现在是不能够在<span style="color:#位进程中调用<span style="color:#位DLL并执行它的代码。这种不可行是因为<span style="color:#位系统的设计造成的。所以说是从根本上的不可能。没有任何技术,也没有任何文档没有记录的方式可以帮助到你。为了做到这个,你必须要装载并初始化WoW64,更不用说内核结构的不同了。这代表着<span style="color:#位的处理器必须在运行中被处理为<span style="color:#位。这个话题在&&中有更为详尽的描述。有一件事情是我建议可以尝试的,就是建立一个使用COM技术创建一个替代程序,你可以阅读&&.
但是,从<span style="color:#位DLL
将资源导入到<span style="color:#位进程中去,是比较容易的,你所需要的就是在调用LoadLibraryEx
的时候制定以下的flag:LOAD_LIBRARY_AS_DATAFILE。
逐渐放弃对<span style="color:#位程序的支持
微软公司为了进一步促进程序向<span style="color:#位迁移,会逐渐在一些版本的Windows操作系统中取消对<span style="color:#位程序的支持。这是一个自然的过程。当然这个过程会比较缓慢,但这个行为已经开始了。
许多管理员可能会知道一个较新的操作系统上的服务器安装和操作模式,叫做Server Core。这个模块正是在持久的“Windows VS Linux”大战中被广泛提起。其中一个支持使用Linux的原因就是Linux支持不需要图像接口就可以安装服务器操作。但是,现在Windows
也有了这种能力。如果你在这种模式中安装系统,你将只获得系统的命令行,而不需要用户界面。
这种能力(Server Core安装)出现在Windows Server 2008.
但是在 Windows Server2008 R2
中,另外一种创新出现使得系统更接近<span style="color:#位。在Windows Server 2008 R2 (Server Core )中,你可以启动或者禁用系统对<span style="color:#位应用程序的支持。更重要的是对<span style="color:#位应用程序的支持默认是被禁用的。所以当你尝试在Server
Core mode中启动<span style="color:#位程序的时候,你会得到一条系统消息告诉你,这是不可以的。如果你需要额外手动启用这种支持,可以通过以下命令实现:
start /w ocsetupServerCore-WOW64
在普通的模式(完全安装),执行<span style="color:#位程序的支持默认是启用的。
这种趋势是十分明显的,因而现在开始创建<span style="color:#位版本的应用是理智的,因为<span style="color:#位的程序能保证在更多的操作系统版本上使用。
Alexey Pahunov的&&也是获取WoW64资料的好地方。Alexey是Microsoft
的员工,他参与了WoW64子系统的开发。
第三讲&将代码导入<span style="color:#位系统的优缺点
你需要带着以下的问题来学习<span style="color:#位系统:“
往<span style="color:#位系统重新编译项目的合理性有多少?”回答这个问题,你需要花费一些时间和精力来思考。在一方面,你可能会因为要提供<span style="color:#位系统的应用支持,使得你在市场上落后于你的对手。在另一方面,你可能会花费了时间在开发<span style="color:#位系统应用上,而这一努力并不能给你带来竞争优势。
以下是一些建议可以用来帮助你做出选择。
应用的生命期
当你的应用有一个比较短的生命期,你暂时还没有必要开发应用的<span style="color:#位版本。WoW64子系统使得陈旧的<span style="color:#位应用在<span style="color:#位机上也能取得比较好的表现。如果你在两年内会停止运营你的产品,你是不需要现在建立一个<span style="color:#位版本的。实践证明向<span style="color:#位系统的迁移是一个非常缓慢平和的过程。可能你的大多数用户在未来的一段时间里还是会仅仅使用你系统的<span style="color:#位版本。你需要知道,本教程是在<span style="color:#09年编写的,这时候大部分用户都还是在使用<span style="color:#位系统。但是很快<span style="color:#位程序,就会变得越来越不自然,会变得落伍的。
如果你的项目有一比较长的开发和维护时间,你需要开始建立你产品的<span style="color:#位版本。当然你可以慢慢来,但是你要记住的是,越迟拥有产品的<span style="color:#位版本,在<span style="color:#位系统上维护<span style="color:#
位版本所带来的问题将越来越多。
程序性能要求
当一个程序重新编译以适应<span style="color:#位版本后,程序可以应用大量的内存资源,它的速度将提高<span style="color:#%-15%。<span style="color:#%-10%的系统提升是因为<span style="color:#位体系结构的特点,如更多的寄存器,所导致的。另外的<span style="color:#%-5%的性能提升是因为抛弃了翻译<span style="color:#位应用使之在<span style="color:#位系统环境运行的WoW64层所带来。
例如,Adobe公司称新版本<span style="color:#位的&Photoshop CS4&比<span style="color:#位版本的快了<span style="color:#%。
需要大量的内存的应用可以期待着有更高的性能提升。比如说,图像编辑器,CAD系统,GSI CAD,数据库和其他的模型包。能将所有的数据存储在内存中,而避免了多余的从硬盘中导入的工作,使得这些应用的速度,可能不是提升了几个百分点,而是成倍的提高。
例如,Alfa-Bank
曾经在他们的IT
基础设施中使用了基于Itanium 2的平台。原因在于他们业务的发展使得现有系统无法支持不断增长的数据量。每个用户的服务延迟变得非常严重。分析显示,系统的瓶颈不在于处理器的表现,而在于<span style="color:#位体系结构与内存子系统的关系上,因为<span style="color:#位体系结构使得只能使用<span style="color:#
Gbyte的服务器地址空间。而它们的数据库却大于<span style="color:# Gbyte。因而导致了子系统的输入输出的临界负荷。Alfa-Bank决定购买一个由<span style="color:#个服务器组成的集群。这两个服务器都拥有<span style="color:#处理器、是基于Itanium
2的、拥有<span style="color:#Gbyte内存的。这个决定使得他们的性能要求达到了可以容忍的程度。这个公司的代表称,引入基于Itanium2
的服务器使得他们解决了重要问题,同时也节约了花费。
在项目中使用第三方库
在决定是否开发你产品的<span style="color:#位版本前,请先确认你所依赖的第三方库是否有<span style="color:#位版本。你需要找出第三方软件<span style="color:#位版本的定价是多少。这些你都可以在库开发者的网站上找到。如果,没有依赖库的<span style="color:#位版本的支持,可以寻找其他支持<span style="color:#位的库作为替代。
你所开发的库的第三方开发者
如果你在开发库,组件,或者其他需要给第三方开发者使用的程序时,你需要尽快的开发一个你的库的<span style="color:#位版本。否则需要<span style="color:#位支持的你的客户会转向其它的替代产品。例如,一些软件和硬件安全方面的开发者,都非常迟的开发<span style="color:#位版本,使得他们的一些客户转向了其他工具来保护自己的产品。
发布你所开发库的<span style="color:#位版本有另一个好处,你可以把它作为一个单独模块来出售。因而想要开发<span style="color:#
位和<span style="color:#位的客户会要购买<span style="color:#个不同的授权。例如Spatial Corporation,就是按这个方式来卖他们的Spatial
ACIS库的。
<span style="color:#位应用
如果你的应用仍然有<span style="color:#位的模块,你需要丢弃它们。<span style="color:#位的Windows版本完全不支持<span style="color:#位的应用。
关于使用<span style="color:#位安装程序的人员,我需要解释一个事情。这样的安装程序仍然在被使用于安装一些<span style="color:#位应用程序。因为对于一些比较流行的<span style="color:#位的安装程序,它们之中包含了一些机制,使得它们能够在运行中被更新的<span style="color:#位安装程序。这可能使得你觉得<span style="color:#位的程序,在<span style="color:#位的系统环境中仍能使用,但这是错误的,请牢记。
汇编器代码
不要忘记了,大量的汇编器代码片是使得创建应用的<span style="color:#位版本更为困难的原因之一。
如果考虑以上提到各种因素后,你决定创建一个你的应用的<span style="color:#位版本,成功不是一定的。你还需要一些必要的工具,以下就是一些你可能会碰到的不愉快的事情。
首先最令人不开心的事情,就是缺少<span style="color:#位的编译器。当我们写本教程的时候(<span style="color:#09),Embarcadero还没有<span style="color:#位C&#43;&#43;编译器,它有期望在<span style="color:#09年年底发布一个。除非你重新编写你自己的部署部分,如使用Microsoft
Visual Studio时可以快速改变配置,否者你没有办法回避这个问题。但其他的问题,就没有像缺少编译器这样,容易被察觉,可能会在你将程序导入一个新的体系结构的时候发生。你可以在开始工作前,做一个调研,看所有需要的组件是否都有<span style="color:#位版本可以使用。不然,你将面临不愉快的事情。
做决定时,还需要注意到一个我们还没提到的问题,修改你的代码使之可以在<span style="color:#位模式下编译所需要的花销。我们会在之后的课程里,告诉你怎样估计这个花销。这个花销可能是非常高昂的,需要经过系统的计划和详细的时间安排。
第五讲&编译<span style="color:#位应用&&&&
我们需要向读者说明,我们不可能涵盖编译一个<span style="color:#位应用的所有细节问题。每个项目有自己的特有的设定,所以你需要特别注意这些设定。本讲只会涵盖对任何程序都重要的步骤,这些步骤只是一个开始,更多的问题需要读者自己去解决。
当你尝试编译一个你产品的<span style="color:#位版本时,首先确认所有必须的<span style="color:#位的库已经成功安装了,而且它们的路径是正确的。例如<span style="color:#位与<span style="color:#位的以“lib”结尾的库文件是不同的,而且在不同的目录中,如果有bug请修改。
注意:如果库是以源代码的形式存在的,那么会存在库项目的<span style="color:#位的配置。注意到,当你修改一个源代码的配置,以编译适合你的<span style="color:#位版本时,你存在可能侵犯了版权的可能性,请谨慎。
Visual C&#43;&#43; 不支持<span style="color:#位的内联汇编(inline assembler),你可以选择一个外部的<span style="color:#位汇编器(如,MASM)或者是重写这些汇编语言。
编译错误和警告的实例
当开始编译你的程序时,你会碰到许多因为显式类型转换和隐式类型转换所带来的问题(explicit and implicit type conversions)。以下是一个例子。
这段代码在<span style="color:#位上成功编译,但在<span style="color:#位上不行,Visual C&#43;&#43;
会产生如下warning
因为函数strlen()返回的类型是size_t,在<span style="color:#位系统上,类型size_t与unsigned
int的类型一致,所以编译器选择&voidfoo(unsigned int)&来调用。但在<span style="color:#位的模式下,size_t与unsigned
int的类型不一致。 size_t
变成了<span style="color:#位,但unsigned int类型仍然保持了<span style="color:#位。因而编译器不知道哪一个foo()函数来调用。
现在来考虑一段由Visual C&#43;&#43;编译<span style="color:#位代码时,所产生的错误:
GetSize()函数返回的类型是 INT_PTR,在<span style="color:#位时,与int类型一致。在<span style="color:#位时,INT_PTR是<span style="color:#位而隐式的转换为<span style="color:#位。因为可能会导致高位&#20540;丢失,这就是系统给出警告的原因。当数组中元素数量超过了INT_MAX,
一个隐式类型转换就有可能会导致错误。去除这个警告和可能错误的方式就是,你应该将len的类型写为INT_PTR
或者是&类型。
在你已经完全了解了<span style="color:#位错误的规律前,都不要去隐藏warning。你有可能会隐藏一个错误,使得之后更难发现它。你可以在之后的课程中,学到更多的关于<span style="color:#位错误的规律和发现他们的方式。你可以同样参考一下文章
size_t&和ptrdiff_t&类型
在大多数关于数据不兼容的编译错误和警告中,我们应该尤其考虑两种类型 size_t
和ptrdiff_t,
这两者在编译<span style="color:#位代码中最容易出现问题。如果你使用的是Visual C&#43;&#43;编译器,这些类型可能被整合到编译器中,你不需要增加库描述文件。但是如果你使用的是GCC,你可能需要添加头文件“stddef.h”。
size_t 是一个C/C&#43;&#43;
基本unsigned integer类型。它是sizeof
操作的返回&#20540;。这种类型的大小之所以这样选择,是因为它可以用来存储理论上的最大数组的大度&#20540;。例如,size_t在<span style="color:#位系统上是<span style="color:#位,在<span style="color:#位系统上是<span style="color:#位。换句话说,你可以安全的用size_t
类型的变量存储一个指针,但是不包括指向函数的指针。这个类型经常作为在循环中的计数器的类型,作为数组索引,用来存储长度,或者用于地址的计算中。以下的这些类型与size_t类&#20284;:SIZE_T, DWORD_PTR, WPARAM, ULONG_PTR。虽然你可以在size_t中存储一个指针,但是最好使用另外一个unsigned
integer类型uintptr_t ——它的名字就反映了它的用途。size_t与uintptr_t
是同义的。
ptrdiff_t是一个C/C&#43;&#43;
基本signed integer类型。这种类型的大小之所以这样选择,是因为它可以用来存储理论上的最大数组的大度&#20540;。例如,ptrdiff _t在<span style="color:#位系统上是<span style="color:#位,在<span style="color:#位系统上是<span style="color:#位。与size_t类&#20284;,你可以安全的用ptrdiff
_t 类型的变量存储一个指针,但是不包括指向函数的指针。ptrdiff_t类型也是表达式&ptr1-ptr2&的返回&#20540;。这个类型经常作为在循环中的计数器的类型,作为数组索引,用来存储长度,或者用于地址的计算中。与它类&#20284;的是SSIZE_T,
LPARAM,INT_PTR, LONG_PTR。与它同义的是intptr_t,intptr_t可以用来存储一个指针。
size_t 和ptrdiff_t被创造就是为保证地址运算的正确性。在较长的时间内,int的类型都与机器字(machine
word)的长度一致,因而int类型变量经常被作为数组索引,或者是用来存储物体和指针大小。因而地址运算也是通过int和unsigned类型来编译的。int类型在大多数的C/C&#43;&#43;教程中出现在循环体做作为索引。下面是一个经典的例子:
当处理器不断的发展,继续扩展int的长度显得不那么合理了。这是由许多因素造成的如:节约使用的内存,最大的兼容性等。因而,许多的出现,来描述基本C与C&#43;&#43;类型之间的关系。所以现在选择一个类型来存储指针和物体大小就不那么容易了。size_t
和ptrdiff_t类型的出现就成了最好的解决方案。它们肯定可以在地址运算中使用。现在如下的代码将成为经典:
这段代码才是能提供最好的安全性,移植性和高性能的代码。在之后的课程中你将学到原因。
size_t 和ptrdiff_t类型可以被称为类型。Memsize
的出现是为了简单的描述所有能作为存储最大数组的长度&#20540;,和作为这样数组的索引的&#20540;的类型。当说到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 length(32bit)
Data length(64bit)
unsigned char
unsigned short
unsigned int
unsigned long
&& 由上表我们可以看出,32位到64位的porting工作,主要就是处理长度变化所引发的各种问题。在32位平台上很多正确的操作,在64位平台上都不再成立。例如:long-&int等,会出现截断问题等。下面将详细阐述具体遇到的问题,并给出修改策略。
截断问题是在32-64porting工作中最容易遇到的问题。
部分的截断问题能够被编译器捕捉到,采用-Wall –W进行编译,永远没有坏处。这种问题处理方法也非常简单,举个例子来说:
&&&&&&& (void)scanf(&%d&,&mylong);// warning: int format, different type arg(arg 2)
&&&&&&& (void)scanf(&%ld&,&mylong);// ok
但有很多情况下,一些截断性问题并不能被良好的诊断出来。
在这种情况下,编译器会直接进行转换(截断处理),编译阶段不报任何警告。当a的数据范围在2G范围内时,不会出问题,但是超出范围,数据将出现问题。
另外,采用了强制转换的方式,使一些隐患被保留了下来,例如:
&&&&&&& (void)scanf(&%d&,(int*)&mylong);//编译成功,但mylong的高位未被赋&#20540;,有可能导致问题。
&& 采用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位系统中,这个&#20540;不是-1,而是;在64位系统中,-1正确的&#20540;应为0xFFFFFFFFFFFFFFFF。要避免这个问题,在声明常量时,使用const,并且带上signed或unsigned。
const signed int INVALID_POINTER_VALUE =0xFFFFFFFF;
上面一行代码将会在32位和64位系统上都运行正常。或者,根据需要适当地使用 “L” 或 “U” 来声明整型常量。
又比如对最高位的设置,通常我们的做法是定义如下的常量0x,但是可移植性更好的方法是使用一个位移表达式:1L &&((sizeof(long) * 8) - 1);
在参数的数据类型是由函数原型定义的情况中,参数应该根据标准规则转换成这种类型
。在参数类型没有指定的情况中,参数会被转换成更大的类型
。在 64 位系统上,整型被转换成 64 位的整型&#20540;,单精度的浮点类型被转换成双精度的浮点类型
。如果返回&#20540;没有指定,那么函数的缺省返回&#20540;是 int 类型的
。避免将有符号整型和无符号整型的和作为 long 类型传递(见符号扩展问题)
请看下面的例子:
long function (long l);
int main () {
&&&&&&&&int i = -2;
&&&&&&&&unsigned k = 1U;
&&&&&&&&long n = function (i &#43; k);
上面这段代码在 64 位系统上会失败,因为表达式&(i &#43; k)&是一个无符号的 32 位表达式,在将其转换成long 类型时,符号并没有得到扩展。解决方案是将一个操作数强制转换成 64 位的类型。
扩充问题(指针范围越界)
扩充问题与代码截断问题刚好相反。请看下面的例子:
int baidu_gunzip(char*inbuf,int len,char* outbuf,int* size)
ret=bd_uncompress((Byte*)outbuf,(uLongf*)size,
&&&&&&&&&&&&&&&&&&&&&&(Byte*)(inbuf&#43;beginpos),inlen);
这是ullib库中baidugz模块的一段代码。在这段代码中,将int型的指针改为long型的指针传递给了bd_uncompress函数。在32位系统中,由于int与long都是32bit,程序没有任何问题,但在64位系统中,将导致指针控制的范围在调用函数中扩展为原来的2倍,这将有可能导致程序出core,或指示的&#20540;不正确。这种问题比较隐蔽,很难发现,但危害极大,需要严&#26684;注意。
解决方法:加强对指针型参数的检查,看是否有范围扩充的问题。
符号扩展问题
要避免有符号数与无符号数的算术运算。在把int与long数&#20540;作对比时,此时产生的数据提升在LP64和ILP32中是有差异的。因为是符号位扩展,所以这个问题很难被发现,只有保证两端的操作数均为signed或均为unsigned,才能从根本上防止此问题的发生。
int i = -2;
unsigned int j = 1;
k = i &#43;
printf(&Answer:%ld\n&, k);
你无法期望例2中的答案是-1,然而,当你在LP64环境中编译此程序时,答案会是。原因在于表达式(i&#43;j)是一个unsigned int表达式,但把它赋&#20540;给k时,符号位没有被扩展。要解决这个问题,两端的操作数只要均为signed或均为unsigned就可。像如下所示:
k = i &#43; (int) j
在 C/C&#43;&#43;&中,表达式是基于结合律、操作符的优先级和一组数学计算规则的。要想让表达式在 32 位和 64 位系统上都可以正确工作,请注意以下规则:
l&两个有符号整数相加的结果是一个有符号整数。
l&int&和 long&类型的两个数相加,结果是一个long 类型的数。
l&如果一个操作数是无符号整数,另外一个操作数是有符号整数,那么表达式的结果就是无符号整数。&&&
l&&int&和 doubule&类型的两个数相加,结果是一个 double 类型的数。此处 int 类型的数在执行加法运算之前转换成 double 类型。
将字符指针和字符字节声明为无符号类型的,这样可以防止 8&位字符的符号扩展问题。
联合体问题(Union)
当联合本中混有不同长度的数据类型时,如果单独使用里面定义的成员,一般没有问题。但在一些复杂的操作中,例如几种类型的混用,可能会导致问题。如例3是一个常见的开源代码包,可在ILP32却不可在LP64环境下运行。代码假定长度为2的unsigned short数组,占用了与long同样的空间,可这在LP64平台上却不正确。
unsigned short len[2];
&&&&正确的方法是检查是否对结构体有特殊的应用,如果有,那么需要在所有代码中仔细检查联合体,以确认所有的数据成员在LP64中都为同等长度。
现代计算机中内存空间都是按照byte划分的,从理论上讲&#20284;乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32 位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。
g&#43;&#43;默认对齐方式是按结构中相临数据类型最长的进行
在32位上,这些类型默认使用一个机器字(4字节)对齐。
在64位上,这些类型默认使用最大两个机器字(8×2=16字节)对齐(按16字节对齐会有好处么?估计于编译器的具体实现相关)
举两个例子说明一下:
struct asdf {
这个结构在32bit操作系统中,sizeof(asdf) =12,在64bit操作系统中,sizeof(asdf)=16
struct asdf {
这个结构在32bit操作系统中,sizeof(asdf) =16,在64bit操作系统中,sizeof(asdf)=32.这里需要说明的是,32位sizeof(long double) = 12, 64位sizeof(longdouble)=16
在跨平台的网络传输过程中,或者文件存取过程中,我们经常会遇到与数据对齐相关的问题,并且为此烦恼不已。64位porting工作,同样需要详细处理这种问题。
幸好在我们的移植过程中,因为平台比较一致,字节对齐问题并不是非常突出。但需要注意字节长度改变带来的问题,举例说明如下:
&&&&&&& size_
例如,我们经常需要把结构存储到文件中去,常规的写法会如下进行:
fwrite(&st, sizeof(struct t), 1, fp)
fread(&st,sizeof(struct t),1,fp)
这种操作,如果针对同一平台,当然没有问题,但是,如果在32位机器上存储的文件,copy到64位系统中去,就会造成严重问题,因为size_t在64位平台上定义为unsigned long型,导致结构按8字节对齐,并且sizeof(struct t)=16,不再是32位平台上的8字节。在网络传输中,问题同样有可能发生,需要严&#26684;注意。
我们可以采用的解决问题的方法有如下几种:
了解平台对齐差异,以及长度变化,修改结构:
例如上例:
&&&&&&& u_&&& //不使用size_t类型
};即可保证两个平台的一致性。
在复杂的机器环境以及网络环境中,还需要进一步采用单字节对齐的方式避免出现其他问题:
&&&&&&& u_
} __attribute__ ((packed));
此时sizeof(struct t) = 7(强制结构安1字节对齐)。
内存分配问题以及指针跳转问题
通常我们写程序的时候,容易引入一些不安全的内存分配方式,以及进行一些不安全的指针跳转操作。例如下面的例子:
int **p; p = (int**)malloc(4 * NO_ELEMENTS);
在上面的代码中,同样只对ILP32有效,对LP64来讲,将是致命的错误。在64位平台上,指针已经是8字节,这将导致空间分配不足,越界操作等。正确的方法是严&#26684;使用sizeof()进行处理:int **p; p =(int**)malloc(sizeof(int*)* NO_ELEMENTS);
在比如:在处理网络交互的报文过程中,经常要处理各种数据结构
typedef struct A{
char * p = &a;
long * p1 = (long*)p;
int * p2 = (int *)(p &#43; 4);
在上面的例子里,同样是进行了错误的偏移计算,导致出错,另外,上面的用法还有可能因为字节对齐方式的不同,产生不同,坚决不推荐使用。
正确的使用方式如下:
long * p1 = &(pA)p-&a;
int * p2 = &(pA)p-&b;
或者如下:
long * p1 = p &#43; (long)(((pA)NULL)-&a);
int * p2 = p &#43;&(long)(((pA)NULL)-&b);
内存消耗问题与性能
在升级到64位操作系统后,内存消耗也会随之增加。例如复杂的结构中(b&#43;tree dict等),会保存大量的指针类型,而这些指针类型在LP64模型中,自身占用内存会大量增加。在内存消耗较多的程序中,升级时要评估升级代价。在必要的情况下,需要修改程序内部逻辑,采用局部偏移代替指针类型,以便节约空间消耗。
另外,由于字节对齐问题,也容易导致结构体的不正常膨胀。通过改变结构中数据排列的先后顺序,能将此问题所带来的影响降到最小,并能减少所需的存储空间。例如把两个32位int&#20540;放在一起,会因为少了填充数据,存储空间也随之减少。
另外,由于64位环境中指针所占用的字节更大,致使原来运行良好的32位代码出现不同程度的缓存问题,具体表现为执行效率降低。可使用工具来分析缓存命中率的变化,以确认性能降低是否由此引起。
字节序问题
字节序问题由来已久,不过,很幸运的是,我们采用的都是x86的结构,因此字节序问题并不会困扰我们的开发工作,简单介绍如下:
一个机器的字长通常包含数个byte,在存储数据的方法上出现了大端点(big endian)和小端点(little endian)两种结构,前者如PowerPC和Sun Sparc,后者如Intel x86系列。大端点机使用机器字长的高字节存储数字逻辑编码的低字节,字节的屋里顺序和逻辑顺序相反;小端点机使用机器字长的高字节存储数字逻辑编码的高字节,字节的屋里顺序和逻辑顺序相同。TCP/IP等传输协议使用的都是大端点序。大端点机和小端点机实际上各有优点,&& 由于Little Endian提供了逻辑顺序与物理顺序的一致性,让编程者摆脱了不一致性所带来的困扰,C语言开发者可以无所顾忌的按照自己的意愿进行强制类型转换,所以现代体系结构几乎都支持Little
Endian。但Big Endian也有其优点,尤其对于汇编程序员:他们对于任意长度的整数,总是可以通过判断Byte 0的bit-7来查看一个整数的正负;对于Little Endian则不得不首先知道当前整数的长度,然后查看最高byte的bit-7来判断其正负。对于这种情况,big endian的开发者可以写出非常高效的代码。
这个差别却给跨平台的程序编写和不同平台主机间的通信带来了相当的困扰。在c/c&#43;&#43;中使用强制类型转换,如int到char数组的转换在有些时候可以写出简洁高效的程序,但字节序的不同确实这种写法有些时候变得很困难,跨平台的程序,以及处理网络数据传输和文件数据转换年的时候,必须要考虑字节序不同的问题。其实在C&#43;&#43;中检测和转换字节序并不困难,写出的程序可以多种多样,基本的思想却是相同的。
检测平台的Endian基本上都是用联合体union来实现的,在union中的数据占有的是最长的那个类型的长度,这样在union中加入不同字节长度的数据并将较长的那个赋&#20540;为1就可以判断了:
typedef union uEndianTest{
&& boolflittle_
&& bool fill[3];
static const EndianTest __Endian_Test__ = { (long)1 };
const bool platform_little_endian = __Endian_Test__.flittle_
这样使用这个 platform_little_endian 就可以检测到当前平台是否是little_endian
Glibc升级以及内核升级带来的问题
升级到64位平台后,由于glibc的升级,以及内核版本的升级,导致个别的函数调用行为与原函数不再一致,对于这种问题,需要不断的总结和积累,慢慢克服。下面介绍一下目前在升级过程中已知的问题。
sendfile函数:在2.4的内核中,sendfile是可以支持文件间的相互copy的,但是在2.6以上的内核中,却只支持文件向socket的copy。为什么去掉这样一个优秀的特性,另人费解。
gethostbyname等函数:包括gethostbyname,gethostbyname_r,getservbyname_r,getservbyport_r等函数,在升级到64位平台后,程序进行-static编译的过程中,会报warning,并且无法消除。经过调查,在使用静态连接的程序中,调用上述函数,的确是存在一定风险的,如果编译机器和运行机器的glibc版本号不一致,会导致严重问题,例如程序crash等。因此,建议调用了上述函数的程序,最好不要使用-static进行编译。事实上,在老版本的gcc上,同样存在上述风险,只是没有报出warning而已。
另外,在不同的版本中,gethostbyname_r的返回&#20540;也存在差异,在异常返回的时候,因此,这几个函数在判断是否成功的时候,需要注意,并不能单纯的判断返回&#20540;。这个问题在ul_gethostbyname_r中已经做了处理,可放心使用。
如果在make的过程中,采用的是gcc的编译器,而不是g&#43;&#43;,可能遇到很多的错误,别紧张,这是由于gcc无法自动和c&#43;&#43;使用的库相连接而导致的。改成g&#43;&#43;进行编译就ok,或者在make的时候指定-lstdc&#43;&#43;。
另外有一些编译要求,请参考《技术部64位开发规范》
http://www.sawin.cn/doc/SP/SPExperience/TheEdge225.htm
http://www-128.ibm.com/developerworks/cn/linux/l-port64.html
ullib支持32位和64位环境的考虑.doc 丘志东
构建64位程序
由于每个为<span style="color:#位系统构建的<span style="color:#位应用程序都有它唯一的设置,我们只描述通用的步骤。
库(libraries)
  在试图构建你的<span style="color:#位程序前,请球包你需要的<span style="color:#位库的版本和路径都安装正确。例如:<span style="color:#bit和<span style="color:#bit库文件应该是在不同的目录下。
  注意:如果库是以源码的形式,那么<span style="color:#位的程序用到它时,该库必须有<span style="color:#位的配置。
汇编程序(Assembler)
VC不提供<span style="color:#位内联的汇编程序。你必须用一个额外的<span style="color:#bit的汇编程序(例如MASM)或者改写c/c&#43;&#43;的汇编代码
兼容性错误和警告的例子
  在开始编译程序时,你也许会遇到很多关于&类型转换&的兼容性错误或警告。例如:
void foo(unsigned char) {}&
void foo(unsigned int) {}&&
void a(const char *str)&
foo(strlen(str));&
  这段代码在<span style="color:#位模式下是编译成功的,但是有警告:
warning C4267: 'argument' : conversion from 'size_t' to'unsigned int', possible loss of data
  但是在<span style="color:#位模式下,VC编译器会有如下错误:
error C2668: 'foo' : ambiguous call to overloadedfunction&
&& .\xxxx.cpp(16): could be 'void foo(unsignedint)'&
&& .\xxxx.cpp(15): or 'void foo(unsignedchar)'&
&& while trying to match the argument list'(size_t)'&
strlen()函数返回的类型是size_t。在<span style="color:#位系统上,size_t与unsigned
int一致,因而编译器会选择&voidfoo(unsigned int)&函数。在<span style="color:#位模式下,size_t就不是unsigned
int了,size_t变成了<span style="color:#位的,而unsigned int还是<span style="color:#位。因而,编译器就不知道该选哪个foo()函数了。
  现在让我们看看用<span style="color:#位编译代码时,VC的另一个警告
CArray&char, char&&
int len = v.GetSize();&&
warning C4244: 'initializing' : conversion from 'INT_PTR'to 'int',&
&&&&&&&&&&&&&&&&possibleloss of data&
GetSize()函数返回的是INT_PTR,在<span style="color:#位的代码里,是和int一致的。但是在<span style="color:#位的代码里,INT_PTR是<span style="color:#位,因而会被隐式的转换成<span style="color:#位的int。这样就会有bit位的丢失。一个隐式的类型转换也许会产生一个错误,例如当一个数组的数目扩展到INT_MAX。为了消除警告和可能的错误,你应该使用INT_PTR或者ptrdiff_t来表示长度。
  不要尝试屏蔽警告。因为你可能会隐藏一个错误,而且也让他更难被发现。你将会在接下来的课程里学习<span style="color:#位下的错误类型和检查纠正他们的方法。
size_t和ptrdiff_t
size_t和ptrdiff_t,是<span style="color:#位编程里和我们最紧密的两种类型,也常常产生兼容性的错误。如果你用VC,这些类型是集成进VC的,因而你不需要库文件。但是如果你用GCC,你将需要用头文件&stddef.h&
size_t是c/c&#43;=的无符号整数。他是sizeof操作返回的结果的数据类型。这个类型的大小是可选的,以便它能够储存一个任意类型的数组的最大的size。例如:size_t在<span style="color:#位系统上是<span style="color:#位,在<span style="color:#位系统上是<span style="color:#位。换言之,你可以用size_t来安全的存储一个指针。不过有一种情况例外,就是指向类函数的指针。size_t常常被用于循环里的计数,数组的索引,大小的表示,和地址之间的计算。下面的几种类型和size_t是类&#20284;的作用:SIZE_T,
DWORD_PTR, WPARAM, ULONG_PTR。尽管你可以把指针存储在size_t里,但是最好还是用另一个无符号的整数类型uintptr_t,因为他的名字更能反映它的功能。size_t和uintptr_t实质上是一样的。
ptrdiff_t是c/c&#43;&#43;的有符号类型。<span style="color:#位系统上他是<span style="color:#位,<span style="color:#位系统上他是<span style="color:#位。像size_t一样,ptrdiff_t能用于安全的存储一个指针(除了指向类函数的指针)。ptrdiff_t是两个指针相减后的结果所用的类型。ptrdiff_t常常被用来做循环的计数、数组的索引和地址之间的计算。下面的几种类型和它是类&#20284;的作用:SSIZE_T,
LPARAM,INT_PTR, LONG_PTR。ptrdiff_t和intptr_t是同义词,不过intptr_t更能表现出它储存的是一个指针。
size_t和ptrdiff_t都可以做地址运算。以前int被认为和机器字一致,能用来表示指针或者对象的索引和大小。因而地址运算使用int或者unsigned来表示。以前常见的代码如下:
for(int i = 0; i & i&#43;&#43;)
  但是当处理器的能力变强大后,上述这样的代码就可能不合理了。需要改成下面这种,在<span style="color:#位上
for(ptrdiff_t i = 0; i & i&#43;&#43;)
  这段代码更安全,性能也很好,可移植。下一课我们会具体介绍。
size_t和ptrdiff_t,是memsize类型。memsize表示所有能储存指针大小或者是储存最大的数组索引的类型。memsize表示一类数据,它在<span style="color:#位机器上是<span style="color:#位的,在<span style="color:#位机器上是<span style="color:#位的。例如:size_t,ptrdiff_t,指针,SIZE_T,LPARAM
64位代码里常见的错误
即便你修改了编译时的所有错误和警告,也不意味着你的<span style="color:#位应用程序就能良好工作。因而我们需要学会如何诊断<span style="color:#位错误。另外i,不要依赖于切换到/WP64,虽然它常常被描述为是查找<span style="color:#位代码问题的好工具。
  切换到/WP64允许程序员找到一些编译<span style="color:#位程序的错误。检查是用下述方式实行的:在<span style="color:#位的代码里,类型用关键字_w64来修饰,这样在检查时会被作为<span style="color:#位的类型来解释。例如:
typedef int MyInt32;
#ifdef _WIN64
typedef __int64 MySS&
typedef int MySS&
void foo() {&
MyInt32 value32 = 10;&
MySSizet size = 20;&
value32 =&
  表达式&value32 = size&,将会在<span style="color:#位系统上截断size,因而可能会有个错误。我们想要诊断这个错误。但是当我们试图去编译<span style="color:#位程序时,都是正确的,而且没有警告。
  为了把程序移植到<span style="color:#位系统上,我们需要切换/Wp64,并且在<span style="color:#位程序里增加关键字__w64。代码如下:
typedef int MyInt32;&
#ifdef _WIN64
typedef __int64 MySS&
typedef int __w64 MySS //Add __w64关键字
void foo() {&
MyInt32 value32 = 10;&
MySSizet size = 20;&
value32 = //C4244 64-bitint assigned to 32-bit int
  这段代码编译完,我们会获得C4244的警告,帮助我们写出可移植到<span style="color:#位平台的代码。
  注意:/Wp64在<span style="color:#位兼容模式下会被忽略,因为所有的类型已经有了确定的size,并且编译器会进行必要的检查。所以,我们即便禁用了/Wp64,也能在编译<span style="color:#位版本时获得C4244的警告。
  所以,切换到/Wp64帮助<span style="color:#位应用程序的开发者位<span style="color:#位编译器做准备。切换到/Wp64,构建<span style="color:#位代码时,会产生编译警告或错误。你需要修改它。
  另外,/Wp64在VC2008里会被弃用,因为我们早该编译<span style="color:#位的应用程序,而不是为可移植到<span style="color:#位上做准备。
<span style="color:#-bit错误
  当我们谈论<span style="color:#位错误时,我们是指一段代码在<span style="color:#位下工作良好,但是编译成<span style="color:#位时却产生错误。<span style="color:#位错误常常以如下形式出现:
*错误的假定类型的大小(例如:假定指针的大小总是<span style="color:#字节)
*处理<span style="color:#位系统上超过<span style="color:#GB的大小的数组
*数据的读写时
*bit操作的代码
*复杂的地址计算
*过时的代码
  事实上,所有的错误都发生在当他重新编译为<span style="color:#位系统时。我们的课程目的是为了总结一些<span style="color:#位的错误,帮助你发现和消除他们。
<span style="color:#位错误的例子
  我们将用<span style="color:#个例子来帮助你弄明白<span style="color:#位的错误是什么。
  第一个底子是用魔术常量数字&4&,来表示指针的大小(在<span style="color:#位的代码下是错误的)。注意这段代码在<span style="color:#位版本上工作良好,也不会有任何警告
size_t pointersCount= 100;
int **arrayOfPointers = (int **)malloc(pointersCount * 4)
  第二个例子是数据读取的机制。这段代码在<span style="color:#位版本上是正确的,但是<span style="color:#位的版本时,这段代码读<span style="color:#位应用程序保存的数据会失败
size_t PixelCount;
fread(&PixelCount, sizeof(PixelCount), 1, inFile)
  接下来的课程我们会讨论更多的<span style="color:#位错误的类型和代码例子。不过人们常常会辩解说当移植代码到非<span style="color:#位架构上也会出现很多错误。
  是的,但是我们的目标不仅仅是学习可移植代码的问题。我们还将解决一些问题,来帮助开发者掌握<span style="color:#平台。
  当我们谈到<span style="color:#位错误时,我们是指这样一类代码:它在<span style="color:#位系统上是正确的,但是移植到<span style="color:#位处理器上却是错误的
检测64位错误的问题
有很多技术来检测程序代码里的错误。让我们来看看最常用的检测方法
Code Reviewer
  这个最古老,最可靠和最经得起检验的找错误的方法是code Review。这个方法依赖于让一些开发者一起阅读代码。不幸的是,这种方法不能应用于针对现代的巨大规模的程序的大规模的测试。
code review也许是个好的方法,能避免<span style="color:#位错误。但是这个方法很昂贵。
静态代码分析
  静态代码分析能帮助开发者减少code review的量和时间。相当多的人喜欢静态代码分析,并且提供大量的方法来检查潜在的问题和风险。静态代码分析的好处是它的可扩展性。你能在一个合理的时间段测试这个程序。并且静态代码分析可以帮助你侦测很多写代码时的错误。
  静态代码分析是侦测<span style="color:#位错误的最常用的方法。以后,当我们讨论<span style="color:#位错误时,我们将会告诉你怎样通过PVS-STUDIO里的Viva64来检查这些错误。下一课我们会学到更多的关于PVS-Studio的静态代码分析的技术
  通过白盒,我们能弄懂函数执行的各个不同代码分支。通过分析和测试我们能覆盖更多的代码。当然,白盒测试作为简单的调试引用程序的方法能找到一些错误。毕竟单步调试是很昂贵的(晕啊,这里的白盒测试居然是单步跟踪!)
黑盒方法(单元测试)
  黑盒方法会显得更好些。这里是指单元测试。单元测试的原则是为独立的单元和方法写一个测试集合,来检查他们的操作的主要模式。一些作者更喜欢单元测试是因为他依赖于程序组织结构的知识。但是我们认为函数测试和单元测试应该被认为是黑盒的,因为单元测试不应该重视函数内部的实现。当测试是在函数写之前就开发了的话,那么单元测试应该是提供一个关于函数的增量的保证。
  单元测试已经证明是很有效的。单元测试的一个优点是你可以独立于你的开发程序来检查程序发生的变化是不是正确的。这就可能让你的测试在很短的时间内跑完,这样开发可以发现错误后立刻修改它。如果一次跑所有的测试不可能,那么可以把长时间的测试分开来执行,例如在晚上跑。这样第二天就可以发现错误了。
  当用单元测试来检查<span style="color:#位错误时,你也许会遇到一些不愉快的事情。为了进行更快的测试,程序员会试图用少量的记录和数据。例如,当你为从一个数组里进行查找功能的函数,设计一个测试时,他不关心是有<span style="color:#0项数据,还是<span style="color:#000000项数据。一百项数据也许足够,但是如果函数处理<span style="color:#000000时,他的速度就会极大的衰减。但是如果你想设计一个有效地测试,来检测你的函数在<span style="color:#位系统上是否正确时,你也许需要多余<span style="color:#亿的数据项。你设想这个函数用<span style="color:#0项和几十亿项的区别。例如下面的例子:
boolFooFind(char *Array, char Value, size_t Size)
&&for (unsigned i = 0; i != S &#43;&#43;i)&
&&&&if (i % 5 == 0 &&Array&== Value)&
#ifdef _WIN64&
&&const size_t BufSize = ui64;&
&&const size_t BufSize = 5242880;&
int _tmain(int, _TCHAR *) {&
&&char *Array =&&(char*)calloc(BufSize, sizeof(char));&
&&if (Array == NULL)&
&&&&std::cout && &Errorallocate memory& && std::&
&&if (FooFind(Array, 33, BufSize))&
&&&&std::cout &&&Find& && std::&
&&free(Array);&
  这个错误在于用循环计数使用了Unsigned。因此这个计数在<span style="color:#位系统上会溢出,产生死循环。
  看到这个例子时,你会发现如果你的程序在<span style="color:#位操作系统上处理大量的数据时,你是不能依靠过时的单元测试的。你必须在测试里补充大数据量的处理。
  不幸的是,写新的单元测试是不够的。我们虽然增加了处理大量数据的用例,但是它的执行时间也会变成。这样你的测试时间可能会增加到超过一天。这个也是我们在修改关于<span style="color:#位版本的测试时需要考虑的问题
  这个方法被认为是开发的最后阶段。手工测试必须存在因为自动化测试不能检测所有错误。但是你不能完全依赖他。
静态代码分析来检查64位错误
静态代码分析
  静态代码分析是一项依赖于学习标记的代码片段来检查错误的技术。标记的代码片段通常包括一些特殊类型的错误。换句话说,一个静态代码检查工具检查的是有错误倾向或者有坏的&#26684;式的代码。这样的代码片段是开发者要学习和决定是否要修改的。
  静态分析器也许是通用的目的(例如,prefast, pc lint, Parasoft c&#43;&#43;test)和特殊的用于查找特定类型错误的目的(例如Chord来检查并行的java程序)。通常静态代码分析工具时相当昂贵的,并且要求你们学会如何使用他们。他们常常提供相当复杂和灵活的子系统。因为静态代码检查工具可能会被很多公司使用和定制规则,来提高软件的质量,因而他以复杂为代价,让开发者在早期阶段检查很多错误。静态代码分析还能帮助管理者更好的管理年轻的新手。
  静态代码分析的主要优点是减少了消除缺陷的代价。早期一个错误被检测,修复它的代价就会越低。因而根据《代码大全》里,在测试阶段修复错误的成本是设计阶段的<span style="color:#倍:
  因而静态代码检查工具减少了开发成本,因为他在设计代码阶段侦测除了很多错误。
静态分析来检查<span style="color:#位错误
  静态代码分析的优点如下:
<span style="color:#、你能检测整个代码。分析器能提供代码的接近完全的覆盖。它保证了你的代码在移植到<span style="color:#位代码以前,已经被检查过一次。
<span style="color:#、可扩展性。无论程序大小,静态代码分析都可以使用。你可以在开发者之间很容易的传播。
<span style="color:#、当刚开始开发一个工程时,开发者会忽略可能的问题,因为不了解<span style="color:#位的特性。分析器能指出危险的区域,并告诉你存在整个问题
<span style="color:#、错误修复的代价降低
<span style="color:#、无论你是移植代码到<span style="color:#位系统上,或者是开发一个<span style="color:#位的代码,你都可以有效的使用静态分析工具
PVS-Studio的Viva64分析器
PVS-Studio是一个的静态分析器的包,它包括一个特殊的静态分析器Viva64来检查<span style="color:#位错误.PVS-Studio分析器是用于windows平台的。它集成在VC10里。PVS-Studio的接口允许你过滤警告,保存和加载警告条目
  分析器的系统要求和VC的要求是一致的:
*操作系统:Win7/Win2000/XP/2003/Vista/2008x86或x64。注意你的操作系统不需要<span style="color:#位的。
*开发环境:VC10(
StandardEdition, Professional Edition, Team Systems
)。你必须有一个组件&x64 compliers and tools&已经安装了,才能测试<span style="color:#位程序。VC的版本里已经集成了,安装时可以选择安装它。
*硬件:PVS-Studio能在不少于<span style="color:#GB的系统上工作(推荐是<span style="color:#GB或者更多)。分析器能利用多核(核越多,则越快)
留意代码里的魔术数字,如果魔术数字是用来参与地址计算,或者大小,bit操作时,要特别留意
  下面的表列出常见的会影响可移植的魔术数字
Table 1 - The basic magic numbers which are dangerouswhen porting 32-bit applications to a 64-bit platform
小心检查你的代码,尽量用sizeof()或者&limits.h&&inttypes.h&里的特殊&#20540;来取代你的魔术数字
下面是一些和魔法数字有关的错误例子,最常见的错误是写类型大小时,如下:
1.&1)&size_t&ArraySize&=&N&*&4;&
2.&&&&intptr_t&*Array&=&(intptr_t&*)malloc(ArraySize);
·&&&&&&&&<span style="color:#)&size_t&ArraySize&=&N&*&sizeof(intptr_t);&
·&&&&&&&&&&&intptr_t&*Array&=&(intptr_t&*)malloc(ArraySize);
·&&&&&&&&&
1.&2)&size_t&values[ARRAY_SIZE];&
2.&&&&memset(values,&ARRAY_SIZE&*&4,&0);&
1.&2)&size_t&values[ARRAY_SIZE];&
2.&&&&memset(values,&ARRAY_SIZE&*&sizeof(size_t),&0);
memset(values,&sizeof(values),&0);&//preferred&alternative
1.&3)&size_t&n,&r;&
2.&&&&n&=&n&&&&(32&-&r);&
1.&3)&size_t&n,&r;&
2.&&&&n&=&n&&&&(CHAR_BIT&*&sizeof(n)&-&r);&&
有时我们需要一个特定的常量,例如:当我们用size_t类型的变量来填充低位的<span style="color:#个字节。在<span style="color:#位程序里,常常如下定义:
1.&//&constant&''&
2.&const&size_t&M&=&0xFFFFFFF0u;&
在<span style="color:#位系统上这就是错的。要发现类&#20284;的错误要花费大量的时间,而且不幸的是,没有其他途径可以查找和纠正这种类型的代码,除非用#ifdef或者一个特殊的宏
1.&#ifdef&_WIN64&
2.&&&#define&CONST3264(a)&(a##i64)&
4.&&&#define&CONST3264(a)&&(a)&
5.&#endif&
7.&const&size_t&M&=&~CONST3264(0xFu);&
有时'-1'备用来表示错误码,或者特殊的标志,常常被写为&0xffffffff&。这种表达式在<span style="color:#位平台上是错误的,你应该显示的定义该数&#20540;位-1.下面就是一个错误的将<span style="color:#xffffffff作为错误码的代码例子:
1.&#define&INVALID_RESULT&(0xFFFFFFFFu)&
3.&size_t&MyStrLen(const&char&*str)&{&
4.&&&if&(str&==&NULL)&
5.&&&&&return&INVALID_RESULT;&
7.&&&return&n;&
10.size_t&len&=&MyStrLen(str);&
11.if&(len&==&(size_t)(-1))&
12.&&ShowError();
为什么是错的呢?因为&(size_t)(-1)&在64位平台上,它的数&#20540;,会转换成大类型的有符号数。-1是0xffffffffffffffff,而不是0xffffffff;因而如果你要用宏的话,那么请这样使用:
1.&#define&INVALID_RESULT&(size_t(-1))&
和<span style="color:#xffffffff有关的错误例子还有:
·&&&&&&&&hFileMapping&=&CreateFileMapping&(&
·&&&&&&&&&&&&(HANDLE)&0xFFFFFFFF,&
·&&&&&&&&&&&&NULL,&
·&&&&&&&&&&&&PAGE_READWRITE,&
·&&&&&&&&&&&&(DWORD)&0,&
·&&&&&&&&&&&&(DWORD)&(szBufIm),&
·&&&&&&&&&&&&(LPCTSTR)&&FileShareNameMap[0]);&
这里<span style="color:#xffffffff也会导致<span style="color:#位系统的错误。
让我们来看下一个错误的<span style="color:#xffffffff例子。
·&&&&&&&&void&foo(void&*ptr)&
·&&&&&&&&{&
·&&&&&&&&&&cout&&&&ptr&&&&&
·&&&&&&&&}&
·&&&&&&&&int&_tmain(int,&_TCHAR&*[])&
·&&&&&&&&{&
·&&&&&&&&&&cout&&&&&-1\t\t&;&
·&&&&&&&&&&foo((void&*)-1);&
·&&&&&&&&&&cout&&&&&0xFFFFFFFF\t&;&
·&&&&&&&&&&foo((void&*)0xFFFFFFFF);&
·&&&&&&&&}
<span style="color:#位的结果
·&&&&&&&&-1&&&&&&&&&&&&&&FFFFFFFF&
·&&&&&&&&<span style="color:#xFFFFFFFF&&&&&&FFFFFFFF&
<span style="color:#的结果
·&&&&&&&&-1&&&&&&&&&&&&&&FFFFFFFFFFFFFFFF&
·&&&&&&&&<span style="color:#xFFFFFFFF&&&&&&FFFFFFFF
变参的函数
<span style="color:#位上不正确的使用printf和scanf的例子
·&&&&&&&&const&char&*invalidFormat&=&&%u&;&
·&&&&&&&&size_t&value&=&SIZE_MAX;&
·&&&&&&&&printf(invalidFormat,&value);&
·&&&&&&&&char&buf[5];&
·&&&&&&&&sprintf(buf,&&%p&,&pointer);&
  第一个例子里,<span style="color:#位平台上size_t不等价于unsigned类型,如果value的&#20540;大于UINT_MAX时,会打印错误
  第二个例子里,指针的大小可能比<span style="color:#位大。因而<span style="color:#位平台上它会导致溢出
  变参的函数被不正确使用是常见的错误,不仅仅是<span style="color:#位平台上才会出现。因此你可以尝试用cout代替print,boost::format或std::stringstream来代替sprintf。例如对于size_t,
windows应用程序如下: 
1.&size_t&s&=&1;&&
2.&printf(&%Iu&,&s);
&&&&& linux应用程序如下:
1.&size_t&s&=&1;&
2.&printf(&%zu&,&s);
同样在使用sccanf时,你也需要注意类&#20284;的关于size的使用问题。为了可移植,你的代码需要如下:
·&&&&&&&&//&PR_SIZET&on&Win64&=&&I&&
·&&&&&&&&//&PR_SIZET&on&Win32&=&&&&
·&&&&&&&&//&PR_SIZET&on&Linux64&=&&z&&
·&&&&&&&&//&...&
·&&&&&&&&size_t&u;&
·&&&&&&&&scanf(&%&&PR_SIZET&&u&,&&u);&
开始进行<span style="color:#位Windows
系统编程之前需要了解的所有信息
本文讨论:
64 位版本 Windows 的背景信息
适当地利用 x64 体系结构
使用 Visual C&#43;&#43; 2005 进行 x64 开发
针对 x64 版本的调试技术
本文使用以下技术:
Windows、Win64、Visual Studio2005
使用 Windows(R)&先锋产品的乐趣之一是能够探究新技术以了解它的工作方式。实际上,我不太喜欢使用操作系统,直到对其内部结构有了一点深入了解之后。因此,当 Windows XP 64
位版本和 Windows Server(R) 2003
出现时,我简直快完蛋了。
Win64 和 x64 CPU
体系结构的优点是:它们与其前任完全不同,但不需要很长的学习过程。尽管开发人员认为迁移到 x64
只是一个重新编译的过程,但事实是我们仍然要在调试器中花费很多时间。拥有 OS
的应用知识十分宝贵。
本文,我将本人在 Win64
体系结构方面的经验归结为一个高手 Win32(R)&程序员迁移到 x64
必备的几个要点。我假设您了解基本的 Win32
概念、基本的 x86
概念以及为什么代码应该在 Win64
上运行。这使我可以将关注的重点放在更重要的内容上。通过本概述,您可以在已经理解的 Win32
体系结构基础上了解到一些重要差异。
系统的一个优点是:与基于 Itanium
的系统不同,您可以在同一台计算机上使用 Win32
或 Win64,而不会导致严重的性能损失。此外,除了 Intel
和 AMD x64
实现之间的几个模糊差异,与 x64
兼容的同一个 Windows
版本应该能够在这两个系统上运行。您不需要在 AMD x64
系统上使用一个 Windows
版本,在 Intel x64
系统上使用另一个版本。
我将讨论分为三大领域:OS
实现细节、适当地利用 x64 CPU
体系结构以及使用 Visual C&#43;&#43;(R)&进行 x64
x64 操作系统
在 Windows
体系结构的所有概述中,我一般喜欢从内存和地址空间开始。尽管 64
位处理器在理论上寻址 16 EB
的内存 (264),但 Win64
目前支持 16 TB(由 44
位表示)。为什么不能在计算机中加载到 16 EB
以使用全部 64
位呢?原因有很多。
对初级用户而言,当前的 x64 CPU
通常只允许访问 40
位(<span style="color:# TB)的物理内存。体系结构(不包括当前硬件)可以将其扩展到 52
位(<span style="color:# PB)。即使没有该限制,映射如此大内存的页表大小也是巨大的。
中一样,可寻址范围分为用户模式区和内核模式区。每个进程都在底部获得其唯一的 8 TB,而内核模式的代码存在于顶部的 8 TB
中,并由所有进程共享。不同版本的 64
位 Windows
具有不同的物理内存限制,如&和&所示。
同样,与 Win32
中一样,x64 页大小为 4 KB。前 64 KB
的地址空间始终不映射,因此您看到的最低有效地址应该是 0x10000。与在 Win32
中不同,系统 DLL
在用户模式的地址范围顶部附近没有默认的加载地址。相反,它们在 4 GB
内存以上加载,通常在 0x7FF
附近的地址上加载。
许多较新的 x64
处理器的一个出色功能是:支持 Windows
用于实现硬件数据执行保护 (DEP)
的 CPU No Execute
平台上存在许多错误和病毒,这是因为 CPU
可以将数据当作合法代码字节执行。CPU
在供数据存储使用的内存中执行从而可终止缓冲区溢出(有意或无意)。通过 DEP,OS
可以在有效代码区域周围设置更清晰的边界,从而使 CPU
在执行超出这些预期边界时捕获到该事件。这推动着为使 Windows
减少受到的攻击而付出的不懈努力。
在为捕获错误而设计的活动中,x64
链接器将可执行文件默认的加载地址指定为在 32
之上。这可以帮助在代码迁移到 Win64
之后能够在现有代码中快速找到这些区域。具体说,如果将指针存储为一个 32
位大小的&#20540;(如 DWORD),那么在 Win64
版本中运行时,它将被有效地截断,从而导致指针无效,进而触发访问冲突。该技巧使查找这些令人讨厌的指针错误变得非常简单。
有关指针和 DWORD
的主题将在 Win64
类型系统中继续讨论。指针有多大?LONG
怎么样?那么句柄(如 HWND)呢?幸好,Microsoft
在进行从 Win16
到 Win32 的复杂转换时,使新的类型模型能够轻松地进一步扩展到 64
位。一般地,除了个别几种情况外,新的 64
位环境中的所有类型(除了指针和 size_t)均与 Win32
中的完全相同。也就是说,<span style="color:#
位指针是 8
字节,而 int、long、DWORD
仍然是 4 字节。在随后讨论进行 Win64
开发时,我将讨论更多有关类型的内容。
Win64 的文件&#26684;式称为 PE32&#43;。几乎从每个角度看,该&#26684;式在结构上都与 Win32 PE
文件完全相同。只是扩展了少数几个字段(例如,头结构中的 ImageBase),删除了一个字段,并更改了一个字段以反映不同的 CPU
类型。&显示已更改的字段。
头之外,没有太多的更改。有几个结构(例如,IMAGE_LOAD_CONFIG和 IMAGE_THUNK_DATA)只是将某些字段扩展到
64 位。添加的 PDATA
区段很有趣,因为它突出了 Win32
实现之间的一个主要差异:异常处理。
环境中,异常处理是基于堆栈的。如果 Win32
函数包含 try/catch
或 try/finally
代码,则编译器将发出在堆栈上创建小型数据块的指令。此外,每个 try
数据块指向先前的 try
数据结构,从而形成了一个链表,其中最新添加的结构位于表头。随着函数的调用和退出,该链表头会不断更新。如果发生异常,OS
将遍历堆栈上的数据块链表,以查找相应的处理程序。我在 1997
月的&中非常详细地描述了该过程,因此这里只做简要说明。
异常处理相比,Win64(包括 x64
和 Itanium
版本)使用了基于表的异常处理。它不会在堆栈上生成任何 try
数据块链表。相反,每个 Win64
可执行文件都包含一个运行时函数表。每个函数表项都包含函数的起始和终结地址,以及一组丰富数据(有关函数中异常处理代码)的位置和函数的堆栈帧布局。请参见 WINNT.H
和 x64 SDK
中的IMAGE_R}

我要回帖

更多关于 64位改32位系统 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信