Visual C++ 64 位迁移的常见问题

64位的win7已经广泛、深入应用了,赶快看看我的32位程序该怎么办吧?

1、用 Visual C++ 创建在 64 位 Windows 操作系统中运行的应用程序时,应注意以下问题:

在 64 位 Windows 操作系统中, int  和  long  是 32 位值。对于计划为 64 位平台编译的程序,应注意不要将指针赋给 32 位变量。在 64 位平台上,指针为 64 位,如果将该指针赋给 32 位变量,则应截断该指针值。

在 64 位 Windows 操作系统中, size_t time_t  和 
ptrdiff_t
 是 64 位值。

在 32 位 Windows 操作系统上 Visual C++ 2005 之前的 Visual C++ 版本中, time_t  是 32 位值。在 Visual C++ 2005 和更高版本中,默认情况下, time_t  是 64 位整数。有关更多信息,请参见时间管理。

应注意代码在哪里采用  int  值并将其作为  size_t  或  time_t  值处理。数字有可能增长得比 32 位数大,并且数据在被传递回 int  存储时有可能被截断。

%x(十六进制  int  格式) printf  修饰符在 64 位 Windows 操作系统中不会按预期的那样工作。它只对传递给它的值的前 32 位值执行操作。

Windows 32 位操作系统使用 %I32x 显示整数。

Windows 64 位操作系统使用 %I64x 显示整数。

%p(指针的十六进制格式)在 64 位 Windows 操作系统中按预期的那样工作。

2、32位变为64位编程后:

SetWindowLong的地方改为SetWindowLongPtr

DWORD xxx=(DWORD)ptr;的地方改为LONG_PTR xxx=(LONG_PTR)ptr;

3、字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:

1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);

3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。 

4、其它移植性考虑:

A、数据类型特别是int相关的类型在不同位数机器的平台下长度不同。

B、为了保证平台的通用性,程序中尽量不要使用long数据库型。可以使用固定大小的数据类型宏定义。

typedef unsigned char int8_t;

C、使用int时也可以使用intptr_t来保证平台的通用性,它在不同的平台上编译时长度不同。

D、程序员的目标是消除所有语法检查器发出的警告,尤其是4311号指针截断警告。
例如:

buff = (puchar)srbcontrol;

  (ulong)buffer += srbcontrol->headerlength;//存在4311号指针截断警告。

//应该把ulong改为uint -ptr,这样才能既在win32上运行,也能在64位上运行

5、为了顺利实现两种平台的源代码级可移植性,程序员应按照以下规则来编写程序或者修改已有程序:

A、不能将指针转换成 int、uint、long、ulong、dword等字长固定为32位的类型
,如果需要对指针做运算,应把指针转换为int-ptr或 uint-ptr,这两种类型在不同平台上才有正确的字长。另外,由于handle实质上是一个指针(void *),因此把handle转换成long或ulong等类型也是不正确的。

B、如果确定需要对指针进行截断,那么应使用ptrtolong()和ptrtoulong()两个函数(在basetsd.h中定义)来进行 ,它们可以屏蔽掉指针截断警告,不过截断的结果不能够再当指针使用了。

C、当某个api函数的 out参数能返回一个指针时,应小心谨慎处理参数 ,在win32中,可以把一个ulong变量的地址进行强制转换后传递给api函数,返回的指针就保存在 ulong变量中,但在win64中,返回的指针有64位,如果使用ulong变量的话就会破坏其他变量的内容,正确并且简单的方法是直接定义一个指针变 量,把指针变量的地址作为参数传递给api函数。

D、谨慎处理多态参数 。在 win32中,一个函数可以用一个dword参数来接受多态参数,即该参数在不同情况下可能具有不同的意义,如解释成整型数或指针。在win64中,如果 一个多态参数可能被解释成指针,那么决不能把多态参数设为dword类型,而应设为uint_ptr或pvoid类型。

E、使用新的 get/setwindowlongptr和get/setclasslongptr api函数 。如果在窗口或类的数据区中存放了指针,就需要调用上面的函数来存取相应的变量,为了帮助程序员在编程中正确处理这一点,头文件 winuser.h把索引值gwl_wndproc、gwl_hinstance、gwl_hwdparent和gwl_userdata的定义取消了, 转而定义了新的索引值gwlp_wndproc、gwlp_hinstance、gwlp_hwdparent和gwlp_userdata。这样下面的
代码将会引起编译错误:

  setwindowlong(hwnd,gwl_wndproc,(long)mywndproc);

  因为gw_wndproc没有定义,正确的代码应为:

  setwindowlongptr(hwnd,gwlp_wndproc,(int_ptr)mywndproc);

F、许多窗口和类的数据结构中包含了指针,因此不能在代码中强行指定偏移量来访问数据成员,而应使用field_offset宏来计算偏移量。

G、由于lparam、wparam和lresult通常用来存放指针或整数,在win64中它们全部被扩展成为64位
,因此不能把它们与dword、ulong、uint、int、int和long等类型混用,否则可能会无意识地把它们截短了。

H、当在Wom64环境下运行32位应用程序时,应用程序只限于在一个处理器上执行 。也就是说应用程序不能够享受64位操作系统所带来的性能上的提升。而单处理器执行会降低32位应用程序在基于Itanium系统上运行时的性能和可伸缩性。为此如果对于性能要求比较高的应用程序或者有负载比较高的服务器,尽量不要采用兼容的方式。

I、转换为LP64指导原则:

请勿假设int和指针的长度相同。

请勿假设int和long的长度相同。

6、最好这样去做:

a、符号扩展:

整数提升: 无论有无符号,都可以在调用类型为int的表达式中使用char、short、枚举类型或者位字段。如果int可以支持初始类型的所有可能,则值会转换为int类型。否则,转换为unsigned int类型。

有符号和无符号整数之间的转换: 将带符号的整数提升为同一类的或者更长类的无符号整数时,该整数首先提升为更长类型的带符号相同值,然后转换为无符号值。

两个有符号整数相加的结果是一个有符号整数。

一个操作数是无符号整数和另外一个操作数是有符号整数,那么表达式的结果就是无符号整数。

int 和 doubule 类型的两个数相加,结果是一个 double 类型的数。

b、使用指针运算,而不是地址运算: 通常指针运算都是独立于数据模型的,而地址运算则可能不是,因此指针运算比地址运算更好。

c、对结构重新压缩: 由于long是64位,因此,尽量将long型的放在结构体开头或者结尾。

d、检查联合类型:

因为其字段的长度可能在ILP32和LP64数据模型之间可能会发生变化。

typedef union{double d; long l[2]}

应当为

typedef union{double d; int l[2]}

e、指定数据类型:

在常量表达式后面增加{u,U,l,L}的某些组合,可以指定类型。

int i = 32;

long j = 1 << I; long j = 1L << I;

f、注意隐式声明:

在某些编译模式下,编译器可能会假设针对在模块中使用却未在外部定义或者声明的任何函数或者变量,使用int类型。

g、sizeof是unsigned long

在LP64环境中,sizeof的有效类型是site_t,该类型是unsigned long。

在IL32环境中,sizeof的有效类型是size_t,该类型是unsighed int。

h、使用强制类型转换说明意图:

i、检查格式字符串转换操作:


对于类型为long或指针的参数,可能需要更改printf,sprintf,scanf,sscanf的格式字符串。指针要转换为%p,long型参数转换为添加一个l在格式字符串前面。

检查直接使用long类型是否仍有意义:

例如:pid_t在32位环境中是long类型,在64位系统中是int类型。

j、对显示32位与64位原型使用#ifdef

某些情况下,一个接口存在对特定的32位和64位版本是不可避免的。可以通过头文件使用_LP64或_ILP32功能测试宏来区分这些版本。

LD_LIBRARY_PATH

可以将32位的lib和64位的lib的路径都设置到这个变量中,也可以将64位的lib路径设置到

LD_LIBRARY_PATH_64中。

k、高级主题:

指令大小仍然是32位的。

整数乘除可以完全在硬件中实现。

结构的传递和返回以不同的方式实现。小型数据结构和某些浮点参数可以直接在寄存器中传递。

所有数据类型现在都与其长度对齐。

系统中存在两种不同的库,32位的库和64位的库。

Ioctl或者ipc机制的接口,通过对这些接口仔细进行编码,并且谨慎地使用#Pragma pack或_Pack指令,可避免对齐问题。

l、进程间通讯:使用相同长度的类型。

m、64位应用程序的优点:


针对64位值更高效地执行算术和逻辑运算。

运算过程使用全寄存器宽度、全寄存器集合新指令。

值得参数传递效率更高。

小型数据结构和浮点数值得参数传递效率更高。

提供了额外的整数寄存器和浮点寄存器。

对于amd64,提供了PC相关寻址模式,从而可提高位置无关代码的执行效率。

n、64位应用程序的缺点:

64位应用程序需要更多的栈空间才能容纳更大的寄存器。

由于使用了更大的指针,因此应用程序需要更大的高速缓存。

64位应用程序不能运行在32位平台上。

o、EOVERFLOW的含义:

许多32位系统调用在遇到64位内核中的大对象时,都会返回EOVERFLOW,由于daddr_t,dev_t,time_t,及其派生类型struct timeval和timespec_t现在包含的64位值,因此可能意味着32位应用程序会遇到更多的EOVERFLOW返回值。

p、谨慎使用ioctl:

请考虑使用两个icotl调用:一个用来处理32位值(IOP32)的指针,另一个用来处理长值(IOPLONG)的指针。

int a,d;

long b;

if (icotl(d, IOP32, &b) == -1)

if (icotl(d, IOPLONG, &a) == -1)

编译此代码并将其作为32位应用程序的一部分运行时,这两个ioctl调用均可正常工作。

但是,编译作为64位应用程序时,这两个调用均可返回。但是,这两个均无法正常工作。第一个,传递的容量太大,其结果和大端,小端字节序有关。第二个,则会溢出,覆盖栈上的附近的地址的内容。

q、派生类型更改:

例如:

#if defined(_LP64)

Typedef ulong_t size_t;

#else

Typedef uint_t size_t;

#endif

r、如何确定系统运行的是32位还是64位的?

RedHeat和CentOS

file /bin/ls 使用file察看任何一个文件在/bin路径下都可以,显示结果为: 

[root@localhost bin]# file date

date: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), stripped

[root@hfgs126 bin]# file wbxgs

wbxgs: ELF 64-bit LSB executable, AMD x86-64, version 1 (SYSV), for GNU/Linux 2.4.0, dynamically linked (uses shared libs), not stripped

7、就像 32 位 Windows 操作系统一样,在 64 位 Windows 操作系统上运行 64 位托管应用程序时,可以创建的对象的大小也有一个 2GB 的限制。

许多情况下,程序集在 32 位或 64 位 CLR 上可以同等地运行。 在 64 位 CLR 上运行时,导致程序的行为发生改变的一些原因有:

结构中包含大小随平台而改变的成员,例如任何指针类型。

指针算法中包含固定大小。

不正确的平台调用或 COM 声明,对句柄使用 Int32,而不是 IntPtr。

将 IntPtr 强制转换为 Int32。

8、本来就充满荆棘的编程之路又要左躲右闪、小心翼翼了。 不知道是幸福,还是可怜。

你可能感兴趣的:(MFC/VC)