閱讀英文

共用方式為


移植問題檢查清單

一般

  • 使用新的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 位。 Visual 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 宣告為 INTULONG,這會產生編譯程式警告。 緩衝區大小,即使正確計算,仍可能超過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,即可避免此問題。

Polymorphism

  • 請小心多型介面。

    請勿建立可接受多型數據之 DWORD 類型參數的函式(或其他固定有效位數類型)。 如果數據可以是指針或整數值,則參數類型應該 UINT_PTRPVOID,而不是 DWORD

    例如,請勿建立可接受類型為 DWORD 值的例外狀況參數陣列的函式。 陣列應該是DWORD_PTR值的陣列。 因此,陣列元素可以保存位址或32位整數值。 一般規則是,如果原始類型是 DWORD ,而且必須是指針寬度,請將它 轉換成DWORD_PTR 值。 這就是為什麼原生 Win32 類型有對應的指標精確度類型。 如果您有以多型方式使用 DWORDULONG 或其他 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 位會遭到覆寫。 這是一個非常微妙,難以找到的錯誤。

  • 請勿將指標 轉換成 INTLONGULONGDWORD

    如果您必須轉換指標以測試某些位、設定或清除位,或操作其內容,請使用 UINT_PTRINT_PTR 類型。 這些類型是調整為 32 位和 64 位 Windows 指標大小的整數類型(例如 32 位 Windows 的 ULONG64 位 Windows 的 _int64 )。 例如,假設您正在移植下列程式代碼:

    ImageBase = (PVOID)((ULONG)ImageBase | 1);
    

    在移植程式中,您會變更程序代碼,如下所示:

    ImageBase = (PVOID)((ULONG_PTR)ImageBase | 1);
    

    在適當的情況下使用 UINT_PTRINT_PTR (如果您不確定是否需要它們,則使用它們時不會造成傷害。 請勿將您的指標轉換成ULONG、LONGINTUINTDWORD 類型

    注意HANDLE 定義為 void \,因此將 *HANDLE 值類型傳送至 ULONG 值以測試、設定或清除低兩個位是程式設計錯誤。

  • 使用 PtrToLongPtrToUlong 來截斷指標。

    如果您必須截斷 32 位值的指標,請使用 PtrToLongPtrToUlong 函式(定義於 Basetsd.h 中)。 此函式會在呼叫期間停用指標截斷警告。

    請仔細使用這些函式。 使用下列其中一個函式截斷指標變數之後,絕不會將產生的 LONGULONG 轉換回指標。 這些函式會截斷位址的上層 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 上,如果數據結構不對齊,則操作結構的例程,例如 RtlCopyMemorymemcpy,將不會有錯誤。 相反地,它們會引發例外狀況。 例如:

    #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位值和指標放在結構的開頭。

    注意 如果可能的話,請避免在同一個頭檔中使用不同的封裝層級。

其他資訊