64ビットOSでの変数のサイズについて

OSの種類として、32ビットOSと64ビットOSがある。
32ビットOSでは、メモリが4Gまでしか認識されないなどのデメリットがあるため、
近年では64ビットOSが主流、だと思う。多分。

しかし時々、64ビットOSでは動作しないアプリケーションもある。(古いフリーソフトなどに良くある)
その要因はソフトによって様々だが、分かりやすい要因の一つとして
変数のサイズ違いが挙げられる。今回は、そのサイズ違いについて説明する。

変数の型には、char、intなど色々あるが、32⇒64ビットの変化で影響があるのは
ポインタ変数(char*とか、void*など)である。
※LinuxOSの場合、Long型のサイズにも影響あり

具体的にはこうなる。

// 32bitOSなら4、64bitOSなら8がiSizeに格納される
size_t iSize = sizeof(void*);

これによってどのような問題が想定されるか。例えば以下のような処理。

int testMethod(void* ptr)
{
    // intは4バイト
    int iAddress = (int)ptr;

    return iAddress;
}

これはポインタのアドレスを数値として取得する処理となるが、
32ビットOSであればint型へのキャストに問題は無い。
しかし、64ビットOSだとサイズが合わず前半4バイト分が切り捨てられる。つまり、こうなる。

0xAABBCCDD12345678 ⇒ 0x12345678

こうなると、正しいアドレス位置が分からなくなり正常な動作が見込めなくなる。
最も、このようなサイズ不一致のキャスト処理はC言語だとワーニングとなり、
C++だとコンパイルエラーとなるため、小規模プロジェクトならまず躓かない。
大規模プロジェクトで、他の(特に問題の無い)ワーニングに埋もれるとその限りではないが。

他に、こういったことも考えられる。

#define ITEM_SIZE 12

struct {
    void* p1;
    void* p2;
    void* p3;
} SampleItem;

void TestMethod2()
{
    SampleItem item;

    // 構造体の0初期化
    memset(&item, 0, ITEM_SIZE);
               :
               :
}

構造体のサイズを定数で設定しているが、64ビットOSだとサイズが合わないため
中途半端にしか初期化されず、不定の値が残る。大問題である。
まあこういったサイズ設定については、普通はこうするだろうけどね。

memset(&item, 0, sizeof(SampleItem));

とまあ、2点ほど例として挙げてみたけど、実際はもっと分かりづらく潜んでるパターンも多い。
環境依存のプログラムを避けるためにも、こういった細かな点から注意していくのはありかと思う。

以下、おまけ。

今回は32⇒64の変化についてだったけど、昔は16⇒32の変化による問題もあった。
16⇒32の場合、ポインタ変数の他、int型のサイズが2バイト⇒4バイトとなった。
その影響を避けるため、コーディングルールで
「intは使わない。2バイトならshort、4バイトならLongに」
「ポインタ変数にはnearまたはfar修飾子を付けて、用途により使い分けよう」
といったことが決められていた時代もあったりしたのを、今回のネタを書いている時に思い出したよ。

以上