/wp64:使编译器警告您潜在的问题
microsoft_ visual c 和 microsoft_ visual c++_ .net 2002 编译器添加了 /wp64 开关,这使您可以测试 32 位代码的 64 位兼容性问题。编译器将发出有关指针截断和不正确转换的警告。将 32 位应用程序迁移到 windows 64 位版本中前面的一个步骤就是打开这个标记,然后就像通常编译代码那样来编译您的代码。第一次会有几个错误。例如,请看下面这个代码片段:
dword i = 0; size_t x = 100; i = x; // c4267: warning c4267: '=' : conversion from // 'size_t' to 'dword', possible loss of data. |
在 32 位的平台上,这段代码能够很好的进行编译,因为 size_t 是 32 位的,但是在 64 位的平台上,size_t 就是 64 位的整数。启用 /wp64 后,编译器将会警告您类似的情况。
其他示例:
void func(dword context) { char* sz = (char*)context; // c4312: warning c4312: // 'type cast' : conversion // from 'dword' to 'char *' of // greater size // do something with sz.. } char* string = "the quick brown fox jumped over the lazy dog."; func((dword)string); // c4311: warning c4311: 'type cast' : // pointer truncation from 'char *' // to 'dword' |
在修复这些错误后,请测试您的 32 位代码。您希望确保 32 位的代码继续按预期那样工作。32 位和 64 位二进制文件应该从相同的代码库中构建。这就是编写不断前进的 windows 应用程序的关键概念。开始时,您需要考虑 32 位和 64 位的问题,并且为应用程序编写可以运行在这两个平台上的代码。
新的数据类型
windows 64 位版本使用 llp64 数据模型。这意味着标准 c 类型 int 和 long 保持为 32 位整数。数据类型 size_t 将映射到处理器词大小(ia32 为 32 位,ia64 为 64 位),并且 __int64 是 64 位整数。在协助迁移 32 位代码时就会完成上述操作。意义在于您可以对应用程序的 32 位版本和 64 版本使用相同的代码库。
还有一个称为 lp64 的数据模型,它将标准的 c 类型 long 映射到 64 位整数,并使 int 保持为 32 位的整数。这种数据模型常见于 unix 平台,但从单个代码库同时创建应用程序的 32 位和 64 位版本时可能有一些困难。您可能注意到了此处的常见主题。32 位平台与 64 位平台的思想就是应该能够从单个代码库中构建两个版本的应用程序。如果无法做到,那么您可能要重新审视您的设计。具有单个代码库就是巨大的胜利,尤其是如果您计划发行两个版本。
多态类型
由于 win32 api 是针对 c 的,在很多情况下,您都需要将整数转换成指针或者相反。在 32 位的硬件上不会有问题,其中指针的大小和整数的大小是相同的,但在 64 位的硬件上却完全不一样。这就是多态类型出现的原因。
对于特定的精度,您可以使用固定精度的数据类型。不管处理器的词大小如何,它们的大小都是一致的。大多数这些类型都在它们的名称中包含精度,可以从下面的表中看出:
表 1. 固定精度的数据类型 | |
类型 | 定义 |
dword32 | 32 位无符号整数 |
dword64 | 64 位无符号整数 |
int32 | 32 位有符号整数 |
int64 | 64 位有符号整数 |
long32 | 32 位有符号整数 |
long64 | 64 位有符号整数 |
uint32 | 无符号 int32 |
uint64 | 无符号 int64 |
ulong32 | 无符号 long32 |
ulong64 | 无符号 long64 |
此外,当您需要数据类型的精度随着处理器词大小变化时,请使用指针精度数据类型。这些类型又称为“多态”数据类型。这些类型通常以 _ptr 后缀结尾,如下面的表格所示:
表 2. 指针精度的数据类型 | |
类型 | 定义 |
dword_ptr | 指针精度的无符号长类型 |
half_ptr | 指针大小的一半。用于包含一个指针和两个小型字段的结构中 |
int_ptr | 指针精度的有符号整型 |
long_ptr | 指针精度的有符号长类型 |
size_t | 指针可以引用的最大字节数。用于必须跨指针的整个范围的计数 |
ssize_t | 有符号 size_t |
uhalf_ptr | 无符号 half_ptr |
uint_ptr | 无符号 int_ptr |
ulong_ptr | 无符号 long_ptr |
lparam | 与 long_ptr 为同义词,(在wtypes.h 中定义) |
wparam | 与 uint_ptr 为同义词,(在 wtypes.h 中定义) |
通过整数参数传递参数或上下文信息的所有 win32 api 都更改为使用这些新的类型。setwindowlong 和 setwindowlongptr 函数都是很好的示例:
旧方法:
long setwindowlong( hwnd hwnd, int nindex, long dwnewlong); |
新的多态方法:
long_ptr setwindowlongptr( hwnd hwnd, int nindex, long_ptr dwnewlong); |
请注意,该函数的 xxxptr 版本使用新的多态类型。对于开发人员而言,通过在窗口的额外数据区域中存储指针来存储窗口的上下文信息是相当常见的。使用 setwindowlong 函数在 windows 32 位版本上存储指针的任何代码必须更改为调用 setwindowlongptr。该更改非常简单并且很快就可以完成,因为大多数更改要求使用多态类型。
另外,windowproc 和 getqueuedcompletionstatus 也是很好的示例:
lresult callback windowproc( hwnd hwnd, uint uimsg, wparam wparam, lparam lparam); bool getqueuedcompletionstatus( handle hcompletionport, lpdword lpnumberofbytes, pulong_ptr lpcompletionkey, lpoverlapped* lpoverlapped, dword dwmilliseconds); |
windowproc 使用 lparam,后者是多态类型。getqueuedcompletionstatus 使用 ulong_ptr,后者也是多态类型。这使那些假设整数的大小与指针大小相同的现有代码可以在进行很少修改的情况下继续工作。
编译器的新优化模式:pogo 和 ltcg
包括 microsoft_ visual studio_ .net 2002 的编译器包含两个新的优化模式:link time code generation(ltcg,又称 whole program optimization)和 profile guided optimization (pogo)。代码优化在 itanium 处理器上比在 x86 平台上更为重要,因为编译器对生产高效代码负有全部责任。这两种优化模式将增加构建次数,并且要求良好的测试方案,尤其是 pogo(因为它需要捕获分析数据)。ltcg 允许链接器跨模块边界执行优化,并且通过生成更好的内嵌代码甚至使用自定义调用约定,在链接阶段实际地生成代码以产生更有效的二进制文件。pogo 使编译器可以根据使用模式来进行优化。它要求两个阶段的构建过程。在第一个阶段中,二进制文件用于使它收集分析数据。在第二个阶段中,对分析数据进行分析后,数据用于指导优化。
杂项性能
当前的编译器擅长于产生高优化的代码。在 itanium 处理器上,编译器负责非常多的工作。因为 itanium 是顺序处理器,编译器必须执行优化(如重新排列指令),以便它们可以并行执行。同样,借助于增加的谓词寄存器,编译器在优化分支处理上有了更多的自由。使用谓词寄存器,编译器可以完全去除一个分支,并可以使用指令的谓词字段来控制是否实际地执行某个指令。这对于性能是有好处的,因为不在小块的代码上跳转以及不再使指令预取无效,编译器可以通知处理器有条件地忽略这些指令。
校准
考虑 itanium 处理器上的校准非常重要。指针必须在 64 位边界上校准,否则您将收到一个校准异常。您可以使用 unaligned 关键字来允许未校准的指针差异,但您将为此付出巨大的性能代价。通常情况下,可以使用 #pragma pack 指令,让编译器处理结构的校准。这使您可以指定用于 32 位和 64 位的不同的校准(在编译时),而不是手动校准代码中的结构。