My friends, my life, my style - James S.F. Hsieh

2/27/2009

x64 Part 3: 容易造成 x64 Migration 失敗的程式陷阱 1-2

  1. 指標存取與型態轉換 指標的 size 在 x86 跟 x64 下有了改變, 以下的程式碼為無法正常運作, 主要是因為指標的值轉換成 DWORD 時已經被截短了, 但是由於濫用強制轉型, 編譯器不會產生任何的警告或是錯誤
    // size of DWORD is 32 bits, size of pointer is 64 bits DWORD dwValueA = 0, dwPointer = (DWORD*)&dwValueA; DWORD dwValueB = *(DWORD*)dwPointer;
    假設指標的大小是固定的, 所以該程式碼是不具有移植性的. 問題的解法是不應該將數值型態與指標型態的資料相互轉換與使用, 避免使用強制轉型或是 static_cast 這樣的手法. 許多問題發生在人們想建立一個萬用的函數界面, 這個介面將來可以傳遞任何的引數, 以下就是個例子, 我們可能用 dwSetting 來傳遞一個指標, 這時候上述的問題就會發生
    STDMETHODIMP CMyCOM::PutSetting(DWORD dwSettingID, DWORD dwSetting) {...} SetWindowLong(hWnd, GWL_WNDPROC, (LONG)MyWndProc); //WIN32 API
  2. 大小相依的運算 由於 VC++ 使用 LLP64 的 data model, 所以只有指標的 size 有所改變, 像是 short, int, long, long long 的大小都跟原本 x86 的環境一致, 但是其他的編譯器與作業系統就不一定會是如此, 例如 Linux, Mac OS X, FreeBSD 就是使用 LP64 的 data model 以下的程式碼結果就會跟預期不同
    unsigned long valueA = 0x12345678, valueB = valueA << 16; // x86: valueB == 0x56780000 // x64 LP64: valueB == 0x0000123456780000
    預期結果有所改變是因為 unsigned long 的大小已經改變了, 而 shift operation 是跟 type 大小有著相依的關係, 使用大小可移植性的 type 可以解決這個問題, 舉個例子, 在 Windows 平台下 DWORD 恆常都是 32 位元無號數, 不管在 MIPS, x86, X64 或是 IA64 都是如此. 其實我們可以用一些手法來產生這樣的型態定義, 並且讓他可以跨機器跨平台. 我們可以使用 ANSI C 定義的 limits.h 來得知 native type 的 size.

#include <limits.h>

// 32 Bits 單位型別 #if (UCHAR_MAX == 0xffffffff) typedef unsigned char DUNIT32; typedef char DUNIT32S; #elif (USHRT_MAX == 0xffffffff) typedef unsigned short DUNIT32; typedef short DUNIT32S; #elif (UINT_MAX == 0xffffffff) typedef unsigned int DUNIT32; typedef int DUNIT32S; #elif (ULONG_MAX == 0xffffffffUL) typedef unsigned long DUNIT32; typedef long DUNIT32S; #else #error 不支援 32 位元寬的型別 #endif