Visual C++ の ARM への移行に関する一般的な問題
Visual C++ で同じソース・コードは x86 または x64 アーキテクチャに比べて ARM のアーキテクチャの異なる結果が生じることがあります。
移行に関する問題のソース
がの場合、x86 からコードを移行するか、ARM のアーキテクチャを x64 が未定義を開始する可能性がある実装定義したソース・コードの構造とアーキテクチャ関連するときに発生する可能性のある多くの問題または未指定の動作。
未定義の動作
C++ 標準で定義されていない例の結果、適切でない操作に時間をかけた浮動小数点値を符号なし整数に変換するか、または負に、レベルが発生した型の bit 数を超えているいくつかの位置によって値を転送する動作。実装定義の動作
C++ 標準では、コンパイラの販売元を定義および文書化するように要求する動作。プログラムでは、実装定義される動作とそれが移植可能ではない場合でも、安全に依存する場合があります。実装で定義されている動作の例では、組み込みデータ型と配置の要件のサイズが含まれます。実装定義の動作によって影響を受ける操作の例では可変個引数リストにアクセスします。未定義の動作
この動作は、非確定的な意図的に C++ の標準リーフ。動作が非確定的考慮されますが、未定義の動作の特定の呼び出しは、コンパイラの実装によって決まります。ただし、結果を事前に決定したり、対応する呼び出し間で一貫した動作を保証するコンパイラの販売元の要件はありません。ドキュメントの要件はありません。未定義の動作の例では、サブ式含む関数の引数が評価される順序はです。
ARM と x86 または C++ の標準とは別に対話する x64 アーキテクチャのハードウェアの違いに他の移行に関する問題は、属性ができます。たとえば、x86 および x64 アーキテクチャの厳密なメモリ モデルは volatile-修飾変数が特定の種類のスレッド間通信を以前転送するためのいくつかのプロパティできます。ただし、ARM のアーキテクチャの弱いメモリ モデルは、この使用方法はサポートされませんが、C++ 標準では、要求します。
![]() |
---|
volatile が x86 および x64 のスレッド間通信の制限付きの実行に使用できるいくつかのプロパティの取得がこれらの追加プロパティは一般に、スレッド間通信を実行するだけでは不十分です。C++ 標準では、このような通信が適切な同期プリミティブを代わりにを使用することによって実行されたことをお勧めします。 |
異なるプラットフォームがこれらの種類の動作を異なる方法で表現する可能性があるため、プラットフォーム間でのソフトウェアを移植すると、特定のプラットフォームの動作に依存している場合、難しいバグの傾向があります。これらの種類の動作の多くを確認することで、安定しているように見えることがありますが、依存関係は少なくとも未定義または未指定の動作の場合、移植性の低いと、エラーです。このドキュメントの引用される動作に依存したりすることはありません。将来のコンパイラまたは CPU の実装で変更できます。
例の移行に関する問題
このドキュメントの残りの部分では、これらの C++ 言語要素の動作の違いが異なるプラットフォームが異なる結果がどのように生成されるかを説明します。
符号なし整数への浮動小数点の変換
ARM アーキテクチャで、32 ビット整数と浮動小数点値の変換を表す整数ができる最も近い値に浮動小数点値を表す整数ができる範囲外にある場合は飽和します。x86 および x64 アーキテクチャでも符号なしである場合、整数変換のラップは -2147483648 に、整数が署名されます。これらのアーキテクチャはいずれも直接より小さな整数型への浮動小数点値の変換をサポートしない; 代わりに、32 の bit への変換が実行されて結果は、ミニに切り捨てられます。
ARM のアーキテクチャ用に、彩度、および切り捨てが、32 ビット整数を飽和の限界させたりする、完全な 32 ビット整数を飽和するには、小さい型が表すことができる小さすぎるなるより大きい値の省略された結果をと unsigned 型への変換が正しく小さい符号なしの型をことを意味します。変換は、32 ビット符号付き整数で正しく飽和しますが、飽和するため切り捨てが正飽和値の -1 と負飽和の値は 0 で、符号付き整数が発生します。小さい符号付き整数への変換は予測不可能である省略された結果を生成します。
x86 および x64 アーキテクチャでは、符号なしの整数変換のラップアラウンドの動作およびオーバーフローの符号付き整数の変換の明示的な評価の組み合わせは、切り捨てとともに、大きすぎるほとんどのシフトの結果を予測不可能になります。
これらのプラットフォームは、整数型への非数 (NaN ではありません) の変換をどのように処理されるかは異なります。ARM、0x00000000 に NaN) ファイルに変換します; x86 および x64 では、0x80000000 に変換します。
浮動小数点変換はに変換されます。値は、整数型の範囲内にあることがわかっている場合にのみ使用できます。
シフト演算子 (<< >>) 動作
ARM アーキテクチャでは、値パターンが繰り返し開始する前に 255 までの bit 左または右に転送できます。x86 および x64) アーキテクチャ パターンで、は 32 の倍数で、パターンのソースが 64 ビット変数である繰り返されます; x64、64 の倍数、およびソフトウェアの実装が使用されている x86 の 256 のすべての複数のケース、そのパターンの繰り返しでは。たとえば、の 32 ビット変数に値 1 は、32 の位置に左に転送し、ARM の結果は 0 ですが、x86 の結果は 1 で、x64 に結果も 1.です。ただし、3 つすべてのプラットフォームの値のソースが 64 ビット変数の場合、結果は 4294967296 で、も 64 x64 の位置を移した、ARM の x86 および 256 の位置の値は折り返されません "の周囲に"。
ソース型の bit 数を超えているシフト演算の結果は未定義であるため、コンパイラは一定の動作が全ての状況で必要ではありません。たとえば、シフトの両方のオペランドがコンパイル時にわからない場合、コンパイラはより、シフトの結果内部ルーチンを使用し、シフト演算の代わりに結果に置き換えることでプログラムを最適化できます。シフト量が CPU によって実装されると同じシフトの式の結果とは異なるには大きすぎて、または負の場合、内部ルーチンの結果は可能性があります。
可変個の引数 (varargs) の動作
ARM アーキテクチャで、スタックに渡されます可変個引数リストからパラメーターは配置します。たとえば、64 ビットのパラメーターは 64 ビットの境界に揃えられます。x86 および x64 に、スタックに渡される引数は、配置の対象外であるため、厳密に詰まりません。この違いにより、printf などの variadic 関数は、x86 または x64 アーキテクチャのある値のサブセットで行う場合でも、可変個引数リストの予想レイアウトが正確に一致する ARM の埋め込みとして指定されているメモリ アドレスを読み取ることができます。次の例について考えます。
// notice that a 64-bit integer is passed to the function, but '%d' is used to read it.
// on x86 and x64 this may work for small values because %d will “parse” the low-32 bits of the argument.
// on ARM the calling convention will align the 64-bit value and the code will print a random value
printf("%d\n", 1LL);
この場合、バグは、正しいファイル形式仕様が使用されていることを確認することで、引数の配置と見なされるように修正できます。このコードは適切です:
// CORRECT: use %I64d for 64-bit integers
printf("%I64d\n", 1LL);
引数の評価順序
ARM、x86 および x64 プロセッサが大きく異なるため、コンパイラの実装に異なる条件、および最適化の異なる機会を参照したりできます。このため、他の要素とともに呼び出し規約と最適化の設定を使用した場合、そのほかの要因が変更されたときにコンパイラは、各アーキテクチャの異なる順序で関数の引数を評価する場合があります。これにより、予期せず変更する特定の評価の順序に依存するアプリケーションの動作が発生する可能性があります。
関数の引数が同じ呼び出しの関数に他の引数に影響を与える副作用があるときにこの種類のエラーが発生することがあります。通常、このような依存関係を回避しやすくなりますが、リークが困難または演算子のオーバーロードによってできます。依存関係によってマスク。このコード例を使用する:
handle memory_handle;
memory_handle->acquire(*p);
これは、これに似たものに正しく定義された、-> と * オーバーロードされた演算子の場合、このコードの変換です:されます。
Handle::acquire(operator->(memory_handle), operator*(p));
と operator->(memory_handle) と operator*(p)間に依存関係がある場合、コードは特定の評価の順序、そこでのようになります。元のコードが可能な依存関係ではありませんが、依存する場合があります。
volatile キーワードの既定の動作
Microsoft C++ コンパイラは、コンパイラ スイッチを使用して指定できるストレージの volatile 修飾子の 2 種類の解釈をサポートします。/volatile:ms スイッチは、そのアーキテクチャの厳密なメモリ モデルに" Microsoft 拡張正確揮発性セマンティクスを厳密な命令が保証され、ようにする Microsoft のコンパイラの x86 および x64 の通常の場合、選択します。/volatile:iso スイッチは、厳密な C++ の標準揮発性セマンティクスを選択して厳密な命令が保証されます。
ARM アーキテクチャで、既定値は、ARM のプロセッサが弱く順序付けられたメモリ モデルを持つため、ARM のソフトウェアに /volatile:ms の拡張機能に依存しないレガシがなく、通常、ソフトウェアとやり取りする必要がないため /volatile:iso です。ただし、拡張機能を使用するには兵器プログラムをコンパイルするために、まだ場合に便利、または必須です。ISO C++ セマンティクスを使用するプログラムを移植するにはたとえば、は、高価場合があります。また、ドライバー ソフトウェアが正しく機能するために、従来のセマンティクスに準拠する必要がある場合があります。このような場合、/volatile:ms スイッチを使用して; ただし、ARM のターゲットの従来の揮発性セマンティクスを再作成するために、コンパイラは個別にまたは volatile の変数の書き込みに基づいてパフォーマンス低下を招きます持つことができる強力な命令を実装するために読み取られるメモリ バリアを挿入する必要があります。
x86 および x64 アーキテクチャで、既定値はこれらのアーキテクチャについては、Microsoft C++ コンパイラを使用して既に作成されているソフトウェアの多くは、/volatile:ms に依存するためです。x86 および x64 プログラムをコンパイルすると、従来の揮発性セマンティクスの不要な信頼を回避し、移植性を昇格させるのに役立つ /volatile:iso スイッチを指定できます。