次の方法で共有


移植問題チェックリスト

全般

  • 新しい 64 ビット セーフな Windows データ型を使用します。

    このドキュメントで前述した新しい 64 ビット セーフ データ型は、Basetsd.h で定義されています。 このヘッダー ファイルは Ntddk.h、Wdm.h、Ntifs.h に含まれている Ntdef.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 で適切な書式指定子を使用します。

    ポインターを 16 進数で出力するには 、%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 に明示的に型キャストします。

  • 16 進定数と符号なし値を使用する場合は注意してください。

    次のアサーションは、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) が 0xFFFF FFFF xxxx xxxx ではなく、0x0000 0000 xxxx xxxx を生成することです。 コンパイラはこれを検出しません。 これを修正するには、次のようにコードを変更します。

    a = a & ~((UINT_PTR)b - 1);
    
  • バッファー サイズを計算するときは注意してください。

    次の点を考慮してください。

    len = ptr2 - ptr1 
    /* len could be greater than 2**32 */
    

    ポインターの算術演算にはPCHARにポインターをキャストします。

    手記lenINT または 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 型 (またはその他の固定精度型) のパラメーターを受け取る関数は作成しないでください。 データにポインターまたは整数値を指定できる場合、パラメーターの型は DWORD ではなく、UINT_PTRまたは PVOID である必要があります。

    たとえば、 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 ビットは上書きされます。 これは非常に微妙で見つけにくいバグです。

  • INTLONGULONG、または 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_PTRINT_PTR を使用します (必要かどうかが不明な場合は、万が一の場合に備えて使用しても害はありません)。 ポインターを ULONGLONGINTUINTDWORD の型にキャストしないでください。

    HANDLEvoid \として定義されているため、*HANDLE 値をULONG 値に型キャストして、下位 2 ビットをテスト、設定、またはクリアすることはプログラミング エラーです。

  • ポインターを切り捨てるには、PtrToLong と PtrToUlong を使用します。

    32 ビット値へのポインターを切り捨てる必要がある場合は、 PtrToLong またはPtrToUlong 関数 ( Basetsd.h で定義) を使用します。 この関数は、呼び出しの間、ポインターの切り捨て警告を無効にします。

    これらの関数は慎重に使用してください。 これらの関数のいずれかを使用してポインター変数を切り捨てた後、結果の LONG または ULONG をポインターにキャストし直すことはありません。 これらの関数は、アドレスの上位 32 ビットを切り捨てます。これは通常、ポインターによって最初に参照されたメモリにアクセスするために必要です。 これらの関数を慎重に考慮せずに使用すると、コードが脆弱になります。

データ構造と構造の配置

  • データ構造ポインターのすべての用途を慎重に調べます。

    一般的な問題の領域を次に示します。

    • ディスクに格納されるか、32 ビット プロセスと交換されるデータ構造。
    • ポインタを使用した明示的および暗黙的な共用体。
    • セキュリティ記述子。
  • FIELD_OFFSET マクロを使用します。

    例えば次が挙げられます。

    struct xx {
       DWORD NumberOfPointers;
       PVOID Pointers[1];
    };
    
    

    64 ビット Windows では次の割り当てが正しくありません。これは、コンパイラが構造体に 8 バイトのアラインメント要件を満たすために追加の 4 バイトで埋め込むためです。

    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;
    }
    

    残念ながら、 UNALIGNED マクロの使用は、Itanium ベースのプロセッサでは非常に高価です。 64 ビットの値とポインターを構造体の先頭に配置することをお勧めします。

    手記 可能であれば、同じヘッダー ファイルで異なるパッキング レベルを使用しないでください。

追加情報