5

C++ 固有の質問です。そこで、プログラムを 32 ビット/64 ビットにする理由についての質問を読みましたが、得られた回答は次のようなものでした (質問が見つからなくて申し訳ありません。数日前に調べたのですが、もう一度見つけられませんでした:( ): 「ポインターの仮定」を行わない限り、再コンパイルするだけで済みます.だから私の質問は、ポインターの仮定とは何ですか?私の理解では、32ビットのポインターと64ビットのポインターがあるので、それは何かと関係があると思います.その . それらの間のコードの違いを示してください. コードを書くときに覚えておくべき他の良い習慣, それは から への変換を容易にするのに役立ちます :) 彼らと例を共有してください

Ps。この投稿があることは知っています: How do you write code that is both 32 bit and 64 bit compatible? しかし、私のような新しいプログラマーにとっては、良い例がなく一般的であると感じました。32ビットのストレージユニットのようなものです。もう少し詳しく説明します (しゃれは意図していません ^^ ) ds.

4

5 に答える 5

6

sizeof()一般に、プログラムの動作は、明示的または暗黙的に (可能な構造体アラインメントも含まれます)、(正確なサイズになるように作成されていない) 型に依存してはならないことを意味します。

ポインターはそれらのサブセットに過ぎず、関連のないポインター型および/または整数間で変換できることに頼るべきではないこともおそらく意味しますintptr_t

同じように、ディスクに書き込まれたものを処理する必要があります。ここでは、組み込み型などのサイズに依存してはいけません。どこでも同じです。

必要なときはいつでも (たとえば外部データ形式のために) のような明示的にサイズ設定された型を使用しますuint32_t

于 2013-02-21T12:04:53.263 に答える
3

「ポインターの仮定」とは、他のデータ型に適合するポインターに依存するコードを記述する場合です。たとえばint copy_of_pointer = ptr;、int が 32 ビット型の場合、ポインターの一部のみが格納されるため、このコードは 64 ビット マシンで壊れます。 .

ポインターがポインター型にのみ格納されている限り、まったく問題ありません。

通常、ポインターは「マシン ワード」のサイズであるため、32 ビット アーキテクチャでは 32 ビット、64 ビット アーキテクチャではすべてのポインターは 64 ビットです。ただし、これが当てはまらないアーキテクチャがいくつかあります。私はそのようなマシンで自分自身で作業したことはありません[「遠く」と「近く」のポインターを持つx86を除いて-しかし、今のところそれを無視しましょう]。

ほとんどのコンパイラは、ポインターが収まらない整数にポインターを変換すると通知するため、警告を有効にすると、ほとんどの問題が明らかになります-警告を修正すると、コードがすぐに機能する可能性がかなり高くなります.

于 2013-02-21T11:59:52.223 に答える
3

整形式のプログラム (つまり、未定義の動作がなく、C++ の構文規則と意味規則に従って作成されたプログラム) の場合、C++ 標準は、プログラムが一連の観察可能な動作のいずれかを持つことを保証します。観察可能な動作は、プログラム内の不特定の動作 (実装定義の動作を含む) により異なります。不特定の動作を回避または解決すると、プログラムは特定の特定の出力を持つことが保証されます。この方法でプログラムを作成すると、32 ビット マシンでも 64 ビット マシンでもプログラムに違いは見られません。

可能な出力が異なるプログラムの単純な (強制的な) 例は次のとおりです。

int main()
{
  std::cout << sizeof(void*) << std::endl;
  return 0;
}

このプログラムは、32 ビット マシンと 64 ビット マシンで出力が異なる可能性があります (必ずしもそうとは限りません)。の結果sizeof(void*)は実装定義です。ただし、実装定義の動作を含むが、明確に定義されているように解決されるプログラムを持つことは確かに可能です。

int main()
{
  int size = sizeof(void*);
  if (size != 4) {
    size = 4;
  }
  std::cout << size << std::endl;
  return 0;
}

このプログラムは4、実装定義の動作を使用しているにもかかわらず、常に を出力します。これはばかげた例ですが、これはint size = 4;プラットフォームに依存しないコードを書くときに現れる場合があります。

したがって、移植可能なコードを作成するためのルールは、未指定の動作を回避または解決することです。

不特定の動作を回避するためのヒントを次に示します。

  1. 基本型のサイズについて、C++ 標準で指定されているサイズを超えると想定しないでください。つまり、acharは少なくとも 8 ビットで、 と は両方ともshort少なくともint16 ビットなどです。

  2. ポインター マジック (ポインター型間のキャストまたは整数型へのポインターの格納) を実行しようとしないでください。

  3. unsigned char*非オブジェクトの値表現を読み取るために a を使用しないでくださいchar(シリアライゼーションまたは関連タスクの場合)。

  4. 避けてくださいreinterpret_cast

  5. オーバーフローまたはアンダーフローする可能性のある操作を実行する場合は注意してください。ビット シフト操作を行うときは、慎重に検討してください。

  6. ポインター型で算術演算を行う場合は注意してください。

  7. 使用しないでくださいvoid*

標準では、未規定または未定義の動作がさらに多く発生します。それらを調べる価値は十分にあります。32 ビット プラットフォームと 64 ビット プラットフォーム間で発生する一般的な相違点について説明している優れた記事がオンラインにいくつかあります。

于 2013-02-21T12:23:13.050 に答える
1

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/vo​​id* は 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 ビット ターゲットに悪影響はありませんか? 機能強化はどこにあり、どれくらい得られますか? 大規模な商用プロジェクトの場合、これらの質問への回答は、多くの場合、ロードマップの重要な目印になります。なぜなら、出発点は既存の「金儲け者」だからです...

于 2013-02-21T12:24:25.403 に答える
1

32 ビット コードと 64 ビット コードの間に違いはありません。C/C++ およびその他のプログラミング言語の目標は、アセンブリ言語ではなく移植性です。

唯一の違いは、コードをコンパイルするディストリビューションです。すべての作業はコンパイラ/リンカーによって自動的に行われるため、それについては考えないでください。

ただし、64ビットディストリビューションでプログラミングしていて、SDLなどの外部ライブラリを使用する必要がある場合、コードをコンパイルするには、外部ライブラリも64ビットでコンパイルする必要があります.

知っておくべきことの 1 つは、ELF ファイルは 32 ビット ディストリビューションよりも 64 ビット ディストリビューションの方が大きくなるということです。これは単なる論理です。

ポインタのポイントは何ですか?ポインターをインクリメント/変更すると、コンパイラーはポインターをポインティング型のサイズからインクリメントします。

含まれる型のサイズは、プロセッサのレジスタ サイズ/作業中のディストリビューションによって定義されます。

しかし、これを気にする必要はありません。コンパイルがすべてを行います。

合計: これが、32 ビット ディストリビューションで 64 ビット ELF ファイルを実行できない理由です。

于 2013-02-21T12:04:09.817 に答える