32 ビット/64 ビット移植の典型的な落とし穴は次のとおりです。
sizeof(void*) == 4 * sizeof(char) というプログラマーによる暗黙の仮定。この想定をしている場合、たとえば配列をそのように割り当てると (「20 個のポインタが必要なので 80 バイトを割り当てる」)、バッファ オーバーランが発生するため、コードが 64 ビットで壊れます。
「子猫殺し」 int x = (int)&something; (逆に、void* ptr = (void*)some_int)。ここでも sizeof(int) == sizeof(void*) の仮定です。これによりオーバーフローは発生しませんが、データが失われます。つまり、ポインターの上位 32 ビットです。
これらの問題は両方とも、タイプのエイリアシング (2 つのタイプ間のバイナリ表現レベルでの同一性 / 相互交換性 / 同等性を仮定する) と呼ばれるクラスのものであり、そのような仮定は一般的です。UN*X のように、time_t、size_t、off_t が int であると仮定するか、Windows では、HANDLE、void*、および long が交換可能であるなど...
データ構造/スタックスペースの使用に関する仮定 (以下の 5. も参照)。C/C++ コードでは、ローカル変数はスタックに割り当てられ、そこで使用されるスペースは、以下の点と、引数を渡すための異なる規則 (32 ビット x86 は通常スタック上にあり、64 ビットx86 の一部はレジスタに含まれています)。32 ビットでデフォルトのスタックサイズをほとんど使用しないコードは、64 ビットでスタック オーバーフロー クラッシュを引き起こす可能性があります。これは、クラッシュの原因として特定するのは比較的簡単ですが、アプリケーションの構成可能性によっては修正が難しい場合があります。
32 ビット コードと 64 ビット コードの間のタイミングの違い (異なるコード サイズ/キャッシュ フットプリント、異なるメモリ アクセス特性/パターン、または異なる呼び出し規則による) は、「調整」を壊す可能性があります。たとえば、 for (int i = 0; i < 1000000; ++i) sleep(0); とします。32ビットと64ビットではタイミングが異なる可能性があります...
最後に、ABI (Application Binary Interface) です。通常、64ビット環境と32ビット環境の間には、ポインターのサイズよりも大きな違いがあります...現在、64ビット環境の2つの主要な「ブランチ」が存在します。 ) および LP64 (UN*X が使用するもの - int は int32_t、long は int64_t、uintptr_t/void* は uint64_t) のサイズの整数に関しては、さまざまなアライメント規則の「細分化」もあります - 一部の環境では long を想定しています、float、または double はそれぞれのサイズで整列しますが、他のものは 4 バイトの倍数で整列すると仮定します。32 ビット Linux では、それらはすべて 4 バイトでアライメントされますが、64 ビット Linux では、float は 4 でアライメントされ、long と double は 8 バイトの倍数でアライメントされます。これらのルールの結果、多くの場合、bith sizeof(struct { ...}) と構造体/クラス メンバーのオフセットは、データ型の宣言が完全に同じであっても、32 ビット環境と 64 ビット環境で異なります。これらの問題は、配列/ベクトルの割り当てに影響を与えるだけでなく、たとえばファイルを介したデータの入出力にも影響します。int b; char c、long d; double e } を 64 ビット用に再コンパイルした同じアプリが読み込むファイルに追加すると、結果は期待したものとはまったく異なります。上記の例は、言語プリミティブ (char、int、long など) に関するものにすぎませんが、もちろん、size_t、off_t、time_t、HANDLE、本質的に重要な構造体/共用体/ class ... - したがって、ここでのエラーのスペースは大きく、}) および構造体/クラス メンバーのオフセットは、データ型の宣言が完全に同じであっても、32 ビット環境と 64 ビット環境で異なります。これらの問題は、配列/ベクトルの割り当てに影響を与えるだけでなく、たとえばファイルを介したデータの入出力にも影響します。int b; char c、long d; double e } を 64 ビット用に再コンパイルした同じアプリが読み込むファイルに追加すると、結果は期待したものとはまったく異なります。上記の例は、言語プリミティブ (char、int、long など) に関するものにすぎませんが、もちろん、size_t、off_t、time_t、HANDLE、本質的に重要な構造体/共用体/ class ... - したがって、ここでのエラーのスペースは大きく、}) および構造体/クラス メンバーのオフセットは、データ型の宣言が完全に同じであっても、32 ビット環境と 64 ビット環境で異なります。これらの問題は、配列/ベクトルの割り当てに影響を与えるだけでなく、たとえばファイルを介したデータの入出力にも影響します。int b; char c、long d; double e } を 64 ビット用に再コンパイルした同じアプリが読み込むファイルに追加すると、結果は期待したものとはまったく異なります。上記の例は、言語プリミティブ (char、int、long など) に関するものにすぎませんが、もちろん、size_t、off_t、time_t、HANDLE、本質的に重要な構造体/共用体/ class ... - したがって、ここでのエラーのスペースは大きく、これらの問題は、配列/ベクトルの割り当てに影響を与えるだけでなく、たとえばファイルを介したデータの入出力にも影響します。int b; char c、long d; double e } を 64 ビット用に再コンパイルした同じアプリが読み込むファイルに追加すると、結果は期待したものとはまったく異なります。上記の例は、言語プリミティブ (char、int、long など) に関するものにすぎませんが、もちろん、size_t、off_t、time_t、HANDLE、本質的に重要な構造体/共用体/ class ... - したがって、ここでのエラーのスペースは大きく、これらの問題は、配列/ベクトルの割り当てに影響を与えるだけでなく、たとえばファイルを介したデータの入出力にも影響します。int b; char c、long d; double e } を 64 ビット用に再コンパイルした同じアプリが読み込むファイルに追加すると、結果は期待したものとはまったく異なります。上記の例は、言語プリミティブ (char、int、long など) に関するものにすぎませんが、もちろん、size_t、off_t、time_t、HANDLE、本質的に重要な構造体/共用体/ class ... - したがって、ここでのエラーのスペースは大きく、
そして、低レベルの違いがあります。たとえば、手動で最適化されたアセンブリ (SSE/SSE2/...) の場合などです。32bit と 64bit ではレジスタ (数) が異なり、引数の受け渡し規則も異なります。これらすべてが最適化の実行方法に大きく影響し、たとえば 32 ビット モードで最高のパフォーマンスを発揮する SSE2 コードを書き直して、64 ビット モードで最高のパフォーマンスを発揮するように拡張する必要がある可能性が非常に高くなります。
32 ビットと 64 ビットで大きく異なるコード設計の制約もあり、特にメモリ割り当て/管理に関しては顕著です。「32ビットで取得できるメモリを最大限に活用する」ように注意深くコーディングされたアプリケーションは、メモリの割り当て/解放、メモリマップファイルの使用、内部キャッシングなどの方法/時期について複雑なロジックを持っています。利用可能な巨大なアドレス空間を「単純に」利用できる64ビットでは有害です。このようなアプリは 64 ビット用に再コンパイルしても問題ありませんが、最大化 32 ビットのピープホール最適化がすべて含まれていない「古い単純な非推奨バージョン」よりもパフォーマンスが低下します。
したがって、最終的には、機能強化 / 向上も重要であり、プログラミングや設計 / 要件など、より多くの作業が必要になります。アプリが 32 ビット環境と 64 ビット環境の両方でクリーンに再コンパイルされ、両方で検証されたとしても、実際には64ビットの恩恵を受けていますか?64ビットでより多くのことを実行/より高速に実行するために、コードロジックに対して実行できる/実行する必要がある変更はありますか? 32 ビットの下位互換性を損なうことなく、これらの変更を行うことができますか? 32 ビット ターゲットに悪影響はありませんか? 機能強化はどこにあり、どれくらい得られますか? 大規模な商用プロジェクトの場合、これらの質問への回答は、多くの場合、ロードマップの重要な目印になります。なぜなら、出発点は既存の「金儲け者」だからです...