常规
使用新的 64 位安全 Windows 数据类型。
本文档前面所述的新的 64 位安全数据类型在 Basetsd.h 中定义。 此头文件包含在 Ntdef.h 中,它包含在 Ntddk.h、Wdm.h 和 Ntifs.h 中。
仔细使用平台编译器宏。
以下假设不再有效:
#ifdef _WIN32 // 32-bit Windows code ... #else // 16-bit Windows code ... #endif
但是,64 位编译器定义_WIN32以实现向后兼容性。
此外,以下假设不再有效:
#ifdef _WIN16 // 16-bit Windows code ... #else // 32-bit Windows code ... #endif
在这种情况下,else 子句可以表示_WIN32或_WIN64。
对 printf 和 wsprintf 使用正确的格式说明符。
使用 %p 在十六进制中打印指针。 这是打印指针的最佳选择。
请注意 ,Visual C++的未来版本将支持 %I 打印多态数据。 它将值视为 64 位 Windows 中的 64 位,将 32 位 Windows 中的 32 位视为 32 位。 视觉C++还将支持 %I64 打印 64 位的值。
了解地址空间。
例如,不要盲目假设地址是内核地址,则必须设置其高序位。 若要获取最低系统地址,请使用 MM_LOWEST_SYSTEM_ADDRESS 宏。
指针算术
执行未签名和已签名的操作时请小心。
考虑以下情况:
ULONG x; LONG y; LONG *pVar1; LONG *pVar2; pVar2 = pVar1 + y * (x - 1);
出现此问题的原因是 x 是未签名的,这使得整个表达式未签名。 除非 y 为负,否则此操作正常。 在这种情况下, y 转换为无符号值,将使用 32 位精度、缩放和添加到 pVar1 来计算表达式。 在 64 位 Windows 上,此 32 位无符号负数将成为较大的 64 位正数,从而得出错误的结果。 若要解决此问题,请将 x 声明为有符号值,或在表达式中将其显式类型转换为 LONG。
使用十六进制常量和无符号值时请小心。
在 64 位系统上,以下断言不属实:
~((UINT64)(PAGE_SIZE-1)) == (UINT64)~(PAGE_SIZE-1) PAGE_SIZE = 0x1000UL // Unsigned long - 32 bits PAGE_SIZE - 1 = 0x00000fff
LHS 表达式:
// Unsigned expansion(UINT64)(PAGE_SIZE -1 ) = 0x0000000000000fff ~((UINT64)(PAGE_SIZE -1 )) = 0xfffffffffffff000
RHS 表达式:
~(PAGE_SIZE-1) = 0xfffff000 (UINT64)(~(PAGE_SIZE - 1)) = 0x00000000fffff000
因此:
~((UINT64)(PAGE_SIZE-1)) != (UINT64)(~(PAGE_SIZE-1))
注意 NOT 操作。
考虑以下情况:
UINT_PTR a; ULONG b; a = a & ~(b - 1);
问题是 ~(b~1)生成0x0000 0000 xxxx xxxx ,而不是0xFFFF FFFF xxxx xxxx。 编译器不会检测到此情况。 若要解决此问题,请按如下所示更改代码:
a = a & ~((UINT_PTR)b - 1);
计算缓冲区大小时请小心。
考虑以下情况:
len = ptr2 - ptr1 /* len could be greater than 2**32 */
将指针强制转换为 PCHAR 进行指针算术。
请注意 ,如果 len 声明为 INT 或 ULONG,则会生成编译器警告。 缓冲区大小(即使计算正确)仍可能超过 ULONG 的容量。
避免使用计算或硬编码指针偏移量。
使用结构时,尽可能使用 FIELD_OFFSET 宏来确定结构成员的偏移量。
避免使用硬编码指针或句柄值。
不要将硬编码指针或句柄(如 (HANDLE)0xFFFFFFFF传递给 ZwCreateSection 等例程。 请改用常量(例如INVALID_HANDLE_VALUE),这些常量可以定义为为每个平台提供适当的值。
请注意,在 64 位 Windows 中,0xFFFFFFFF与 -1 不同。
例如:
DWORD index = 0; CHAR *p; // if (p[index-1] == '0') causes access violation on 64-bit Windows!
在 32 位计算机上:
p[index-1] == p[0xffffffff] == p[-1]
在 64 位计算机上:
p[index-1] == p[0x00000000ffffffff] != p[-1]
通过将索引类型从 DWORD 更改为DWORD_PTR,可以避免此问题。
多态性
请谨慎使用多态接口。
不要创建接受多态数据的 DWORD 类型参数(或其他固定精度类型的参数)的函数。 如果数据可以是指针或整数值,则参数类型应 UINT_PTR 或 PVOID,而不是 DWORD。
例如,不要创建接受类型为 DWORD 值的异常参数数组的函数。 该数组应是DWORD_PTR值的数组。 因此,数组元素可以保存地址或 32 位整型值。 一般规则是,如果原始类型为 DWORD ,并且需要指针宽度,请将其转换为 DWORD_PTR 值。 这就是为什么本机 Win32 类型有相应的指针精度类型的原因。 如果代码采用多态方式使用 DWORD、 ULONG 或其他 32 位类型(也就是说,你确实希望参数或结构成员保存地址),请使用 UINT_PTR 代替当前类型。
调用具有指针 OUT 参数的函数时请小心。
请勿执行此操作:
void GetBufferAddress(OUT PULONG *ptr); { *ptr=0x1000100010001000; } void foo() { ULONG bufAddress; // // This call causes memory corruption. // GetBufferAddress((PULONG *)&bufAddress); }
将 bufAddress 类型转换为 (PULONG *) 会阻止编译器错误。 但是,GetBufferAddress 会将 64 位值写入 &bufAddress 的内存位置。 由于 bufAddress 只是一个 32 位值,因此 bufAddress 后面的 32 位将被覆盖。 这是一个非常微妙、难以找到的 bug。
不要将指针 强制转换为 INT、 LONG、 ULONG 或 DWORD。
如果必须强制转换指针以测试某些位、设置或清除位,或者操作其内容,请使用 UINT_PTR 或 INT_PTR 类型。 这些类型是缩放为 32 位和 64 位 Windows 的指针大小的整型类型(例如, 32 位 Windows 的 ULONG 和 64 位 Windows 的 _int64 )。 例如,假设要移植以下代码:
ImageBase = (PVOID)((ULONG)ImageBase | 1);
在移植过程中,将按如下所示更改代码:
ImageBase = (PVOID)((ULONG_PTR)ImageBase | 1);
在适当情况下使用 UINT_PTR 和 INT_PTR (如果不确定它们是否是必需的),则使用它们时不会造成任何损害。 不要将指针强制转换为 ULONG、LONG、INT、UINT 或 DWORD 类型。
注意HANDLE 定义为 void \,因此将 *HANDLE 值键入到 ULONG 值以测试、设置或清除低两位是编程错误。
使用 PtrToLong 和 PtrToUlong 截断指针。
如果必须截断指向 32 位值的指针,请使用 PtrToLong 或 PtrToUlong 函数(在 Basetsd.h 中定义)。 此函数在调用期间禁用指针截断警告。
请仔细使用这些函数。 使用这些函数之一截断指针变量后,切勿将生成的 LONG 或 ULONG 转换回指针。 这些函数截断地址的上 32 位,这通常需要访问最初由指针引用的内存。 如果不仔细考虑,使用这些函数将导致代码脆弱。
数据结构和结构对齐
仔细检查数据结构指针的所有用法。
下面是常见问题领域:
- 存储在磁盘或与 32 位进程交换的数据结构。
- 具有指针的显式和隐式联合。
- 安全描述符。
使用 FIELD_OFFSET 宏。
例如:
struct xx { DWORD NumberOfPointers; PVOID Pointers[1]; };
在 64 位 Windows 中,以下分配不正确,因为编译器会用额外的 4 个字节填充结构,以使 8 字节对齐要求:
malloc(sizeof(DWORD)+100*sizeof(PVOID));
下面介绍如何正确执行此操作:
malloc(FIELD_OFFSET(struct xx, Pointers) +100*sizeof(PVOID));
使用 TYPE_ALIGNMENT 宏。
TYPE_ALIGNMENT宏返回当前平台上给定数据类型的对齐要求。 例如:
TYPE_ALIGNMENT(KFLOATING_SAVE) == 4 on x86, 8 on Itanium TYPE_ALIGNMENT(UCHAR) == 1 everywhere
例如,如下代码:
ProbeForRead(UserBuffer, UserBufferLength, sizeof(ULONG));
更改为:
ProbeForRead(UserBuffer, UserBufferLength, TYPE_ALIGNMENT(ULONG));
监视公共内核结构中的数据类型更改。
例如, IO_STATUS_BLOCK结构中的信息 字段现在的类型 为ULONG_PTR。
使用结构打包指令时要小心。
在 64 位 Windows 上,如果数据结构不对齐,则处理结构(如 RtlCopyMemory 和 memcpy)的例程不会出错。 相反,它们将引发异常。 例如:
#pragma pack (1) /* also set by /Zp switch */ struct Buffer { ULONG size; void *ptr; }; void SetPointer(void *p) { struct Buffer s; s.ptr = p; /* will cause alignment fault */ ... }
可以使用 UNALIGNED 宏解决此问题:
void SetPointer(void *p) { struct Buffer s; *(UNALIGNED void *)&s.ptr = p; }
遗憾的是,在 基于 Itanium 的处理器上使用 UNALIGNED 宏非常昂贵。 更好的解决方案是在结构的开头放置 64 位值和指针。
请注意 ,如果可能,请避免在同一头文件中使用不同的打包级别。
其他信息
为 64 位 Windows 做好准备(用户模式应用程序移植指南)