Von Bedeutung
C++ 動的デバッグは現在プレビュー段階 です。 この情報は、リリース前に大幅に変更される可能性があるプレリリース機能に関連しています。 Microsoft は、ここに記載されている情報に関して、明示または黙示を問わず、一切の保証を行いません。
Visual Studio 2022 バージョン 17.14 Preview 2 以降で使用できるこのプレビュー機能は、x64 プロジェクトにのみ適用されます。
C++ 動的デバッグを使用すると、最適化されていないかのように最適化されたコードをデバッグできます。 この機能は、高いフレーム レートを必要とするゲーム開発者など、最適化されたコードのパフォーマンス上の利点を必要とする開発者に役立ちます。 C++ 動的デバッグを使用すると、最適化されたビルドのパフォーマンス上の利点を犠牲にすることなく、最適化されていないコードのデバッグ エクスペリエンスを楽しむことができます。
最適化されたコードのデバッグには課題があります。 コンパイラは、コードを最適化するために命令の位置を変更し、再構成します。 結果はより効率的なコードですが、次のことを意味します。
- オプティマイザーは、ローカル変数を削除したり、デバッガーに不明な場所に移動したりできます。
- オプティマイザーがコード ブロックをマージするときに、関数内のコードがソース コードと一致しなくなる可能性があります。
- オプティマイザーが 2 つの関数をマージすると、呼び出し履歴の関数名が間違っている可能性があります。
以前は、開発者は、最適化されたコードのデバッグ中にこれらの問題やその他の問題に対処していました。 C++ 動的デバッグを使用すると、最適化されていないかのように最適化されたコードにステップ インできるため、これらの課題は解消されました。
最適化されたバイナリを生成するだけでなく、 /dynamicdeopt
を使用してコンパイルすると、デバッグ中に使用される最適化されていないバイナリが生成されます。 ブレークポイントを追加するか、関数 ( __forceinline
関数を含む) にステップインすると、デバッガーは最適化されていないバイナリを読み込みます。 その後、最適化されたコードではなく、最適化されていない関数のコードをデバッグできます。 最適化されていないコードをデバッグしているかのようにデバッグできますが、残りのプログラムでは最適化されたコードのパフォーマンス上の利点が得られます。
C++ 動的デバッグを試す
まず、最適化されたコードをデバッグする場合の内容を確認します。 その後、C++ 動的デバッグによってプロセスがどのように簡略化されるかを確認できます。
Visual Studio で新しい C++ コンソール アプリケーション プロジェクトを作成します。 ConsoleApplication.cpp ファイルの内容を次のコードに置き換えます。
// Code generated by GitHub Copilot #include <iostream> #include <chrono> #include <thread> using namespace std; int step = 0; const int rows = 20; const int cols = 40; void printGrid(int grid[rows][cols]) { cout << "Step: " << step << endl; for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { cout << (grid[i][j] ? '*' : ' '); } cout << endl; } } int countNeighbors(int grid[rows][cols], int x, int y) { int count = 0; for (int i = -1; i <= 1; ++i) { for (int j = -1; j <= 1; ++j) { if (i == 0 && j == 0) { continue; } int ni = x + i; int nj = y + j; if (ni >= 0 && ni < rows && nj >= 0 && nj < cols) { count += grid[ni][nj]; } } } return count; } void updateGrid(int grid[rows][cols]) { int newGrid[rows][cols] = { 0 }; for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { int neighbors = countNeighbors(grid, i, j); if (grid[i][j] == 1) { newGrid[i][j] = (neighbors < 2 || neighbors > 3) ? 0 : 1; } else { newGrid[i][j] = (neighbors == 3) ? 1 : 0; } } } // Copy newGrid back to grid for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { grid[i][j] = newGrid[i][j]; } } } int main() { int grid[rows][cols] = { 0 }; // Initial configuration (a simple glider) grid[1][2] = 1; grid[2][3] = 1; grid[3][1] = 1; grid[3][2] = 1; grid[3][3] = 1; while (true) { printGrid(grid); updateGrid(grid); std::this_thread::sleep_for(std::chrono::milliseconds(100)); cout << "\033[H\033[J"; // Clear the screen step++; } return 0; }
[ソリューション構成] ドロップダウン リストを [リリース] に変更します。 ソリューション プラットフォームのドロップダウン リストが x64 に設定されていることを確認します。
[ビルド]>[ソリューションのビルド]を選択してリビルドします。
int neighbors = countNeighbors(grid, i, j);
内の 55 行目にあるupdateGrid()
にブレークポイントを設定します。 プログラムを実行します。ブレークポイントにヒットしたら、[ ローカル] ウィンドウを表示します。 メイン メニューで、 デバッグ>Windows>Locals を選択します。 [
i
ウィンドウにj
またはの値が表示されないことがわかります。 コンパイラーが最適化によってそれらを削除しました。19 行目の
cout << (grid[i][j] ? '*' : ' ');
で、printGrid()
にブレークポイントを設定してみてください。 できません。 コンパイラがコードを最適化したため、この動作が予想されます。
プログラムを停止し、C++ 動的デバッグを有効にして、もう一度やり直してください
ソリューション エクスプローラーで、プロジェクトを右クリックし、[プロパティ] を選択してプロジェクトのプロパティ ページを開きます。
[ 詳細設定]>[C++ 動的デバッグを使用する]を選択し、設定を [はい] に変更します。
プロパティ ページが開き、[構成プロパティ] > [詳細設定] > [C++ 動的デバッグを使用する] が表示されます。 プロパティは [はい] に設定されています。
この手順では、
/dynamicdeopt
スイッチをコンパイラとリンカーに追加します。 バックグラウンドでは、C++ 最適化スイッチの/GL
と/OPT:ICF
もオフになります。 この設定では、コマンド ラインに手動で追加したスイッチや、設定されている他の最適化スイッチ (/O1
など) は上書きされません。[ビルド]>[ソリューションのビルド]を選択してリビルドします。 ビルド診断コード
MSB8088
が表示されます。これは、動的デバッグとプログラム全体の最適化に互換性がされていないことを示します。 このエラーは、コンパイル中にプログラム全体の最適化 (/GL
) が自動的にオフになっていることを意味します。プロジェクトのプロパティでプログラム全体の最適化を手動でオフにすることができます。 [構成プロパティ]>[Advanced>ホール プログラムの最適化] を選択し、設定を [オフ] に変更します。
MSB8088
は警告として扱われますが、今後のバージョンの Visual Studio ではエラーとして扱われる可能性があります。アプリを再実行します。
55 行目でブレークポイントにヒットすると、[
i
ウィンドウにj
との値が表示されます。 [呼び出し履歴] ウィンドウには、updateGrid()
が最適化されておらず、ファイル名がlife.alt.exe
されていることを示します。 この代替バイナリは、最適化されたコードをデバッグするために使用されます。ブレークポイントが関数 updateGrid に表示されます。 呼び出し履歴は、関数が最適化されておらず、ファイル名が life.alt.exeされていることを示しています。 [ローカル] ウィンドウには、i と j の値と、関数内の他のローカル変数が表示されます。
updateGrid()
関数は、ブレークポイントを設定するため、必要に応じて最適化されません。 デバッグ中に最適化された関数をステップオーバーしても、非最適化されることはありません。 関数にステップ インすると、最適化が解除されます。 関数を最適化解除する主な方法は、関数にブレークポイントを設定するか、それにステップ インすることです。[ 呼び出し履歴] ウィンドウで関数を最適化解除することもできます。 関数または選択した関数のグループを右クリックし、 次のエントリで [最適化解除] を選択します。 この機能は、呼び出し履歴の他の場所にブレークポイントを設定していない最適化された関数でローカル変数を表示する場合に便利です。 この方法で最適化されていない関数は、[ ブレークポイント ] ウィンドウで、 最適化解除された関数という名前のブレークポイント グループとしてグループ化されます。 ブレークポイント グループを削除すると、関連付けられている関数は最適化された状態に戻ります。
条件付きブレークポイントと依存ブレークポイントを使用する
cout << (grid[i][j] ? '*' : ' ');
の 19 行目のprintGrid()
にブレークポイントをもう一度設定してみてください。 これで動作します。 関数にブレークポイントを設定すると、正常にデバッグできるように最適化解除されます。19 行目のブレークポイントを右クリックし、[ 条件] を選択し、条件を
i == 10 && j== 10
に設定します。 次に、 次のブレークポイントにヒットした場合にのみ有効にするチェックボックス をオンにします。 ドロップダウン リストから 55 行目のブレークポイントを選択します。 19 行目のブレークポイントは、50 行目のブレークポイントが最初にヒットし、次にgrid[10][10]
がコンソールに出力されるまでヒットしません。ポイントは、最適化された関数で条件付きブレークポイントと依存ブレークポイントを設定し、最適化されたビルドでデバッガーが使用できない可能性があるローカル変数とコード行を使用できることです。
条件付きブレークポイントが 19 行目に表示されます。cout < < (grid[i][j] ?'*' : ' ');. 条件は i == 10 & j== 10 に設定されます。 [次のブレークポイントにヒットした場合にのみ有効にする] チェック ボックスがオンになっています。 ブレークポイントのドロップダウンリストは、life.cppの55行目に設定されています。
アプリの実行を続行します。 19 行目のブレークポイントにヒットしたら、15 行目を右クリックし 、[次のステートメントの設定 ] を選択してループをもう一度再実行できます。
19 行目の cout < < (grid[i][j] ? '*' : ' ') に条件付きおよび依存ブレークポイントがヒットしました。 [ローカル] ウィンドウには、i と j の値と、関数内の他のローカル変数が表示されます。 [呼び出し履歴] ウィンドウには、関数が最適化されておらず、ファイル名が life.alt.exeされていることを示します。
最適化されていない関数を最適化された状態に戻すには、すべてのブレークポイントを削除します。 Visual Studio のメイン メニューで、[デバッグ] >[すべてのブレークポイントの削除] を選択します。 その後、すべての関数は最適化された状態に戻ります。
このチュートリアルでは行わなかった [ 呼び出し履歴 ] ウィンドウの [ 次のエントリで最適化 解除] オプションを使用してブレークポイントを追加した場合は、[最適化解除 された関数 ] グループを右クリックし、[ 削除 ] を選択して、そのグループ内の関数のみを最適化された状態に戻すことができます。
[ブレークポイント] ウィンドウには、最適化されていない関数グループが表示されます。 グループが選択され、コンテキスト メニューが開き、[ブレークポイント グループの削除] が選択されています。
C++ 動的デバッグを無効にする
最適化されたコードを最適化せずにデバッグする必要がある場合や、最適化されたコードにブレークポイントを配置し、ブレークポイントがヒットしたときにコードを最適化し続ける必要がある場合があります。 ブレークポイントにヒットしたときに動的デバッグを無効にしたり、コードを最適化しないようにするには、いくつかの方法があります。
- Visual Studio のメイン メニューで、 Tools>Options>Debugging>General を選択します。 可能な場合は、 デバッグされた関数を自動的に最適化解除する (.NET 8 以降、C++ 動的デバッグ) チェック ボックスをオフにします。 次回デバッガーが起動しても、コードは最適化されたままになります。
- 多くの動的デバッグ ブレークポイントは、最適化されたバイナリと最適化されていないバイナリの 2 つのブレークポイントです。 [ブレークポイント] ウィンドウ で 、[ 列の表示>Function] を選択します。
alt
バイナリに関連付けられているブレークポイントをクリアします。 ペア内のもう 1 つのブレークポイントは、最適化されたコードで中断されます。 - デバッグ中は、Visual Studio のメイン メニューで[デバッグ]>Windows>Disassembly を選択します。 フォーカスがあることを確認します。 逆アセンブル ウィンドウを使用して関数にステップ インしても、関数は最適化されません。
-
/dynamicdeopt
、cl.exe
、およびlib.exe
にlink.exe
を渡さないようにして、動的デバッグを完全に無効にします。 サード パーティ製のライブラリを使用していて再構築できない場合は、最終的な/dynamicdeopt
中にlink.exe
を渡して、そのバイナリの動的デバッグを無効にしないでください。 - 1 つのバイナリ (
test.dll
など) の動的デバッグをすばやく無効にするには、alt
バイナリの名前を変更または削除します (たとえば、test.alt.dll
)。 - 1 つ以上の
.cpp
ファイルの動的デバッグを無効にするには、ビルド時に/dynamicdeopt
を渡さないでください。 プロジェクトの残りの部分は、動的デバッグを使用してビルドされます。
Unreal Engine で C++ 動的デバッグを有効にする
Unreal Engine 5.6 では、Unreal ビルド ツールと Unreal ビルド アクセラレータの両方で C++ 動的デバッグがサポートされています。 有効にするには、次の 2 つの方法があります。
プロジェクトの
Target.cs
ファイルを変更して、WindowsPlatform.bDynamicDebugging = true
を含めます。開発エディターの構成を使用し、次を含むように
BuildConfiguration.xml
を変更します。<WindowsPlatform> <bDynamicDebugging>true</bDynamicDebugging> </WindowsPlatform>
Unreal Engine 5.5以前の場合、Unreal Build Tool の変更点をGitHubからリポジトリにチェリーピックします。 次に、上記のように bDynamicDebugging
を有効にします。 Unreal Engine 5.6 の Unreal Build Accelerator も使用する必要があります。 ue5-main の最新ビットを使用するか、次を BuildConfiguration.xml
に追加して UBA を無効にします。
<BuildConfiguration>
<bAllowUBAExecutor>false</bAllowUBAExecutor>
<bAllowUBALocalExecutor>false</bAllowUBALocalExecutor>
</BuildConfiguration>
Unreal Engine のビルド方法の構成の詳細については、「 ビルド構成」を参照してください。
トラブルシューティング
最適化されていない関数でブレークポイントがヒットしない場合:
[Deoptimized]
フレームからステップ アウトする場合は、ブレークポイントによって呼び出し元が最適化解除されているか、現在の関数に進む途中で呼び出し元にステップインした場合を除き、最適化されたコードを使用している可能性があります。alt.exe
ファイルとalt.pdb
ファイルがビルドされていることを確認します。test.exe
とtest.pdb
の場合、test.alt.exe
とtest.alt.pdb
は同じディレクトリに存在する必要があります。 この記事に従って、適切なビルド スイッチが設定されていることを確認します。debug directory
エントリは、最適化されたデバッグに使用するtest.exe
バイナリを見つけるためにデバッガーが使用するalt
に存在します。 x64 ネイティブの Visual Studio コマンド プロンプトを開き、link /dump /headers <your executable.exe>
を実行して、deopt
エントリが存在するかどうかを確認します。 次の例の最後の行に示すように、deopt
エントリがType
列に表示されます。Debug Directories Time Type Size RVA Pointer -------- ------- -------- -------- -------- 67CF0DA2 cv 30 00076330 75330 Format: RSDS, {7290497A-E223-4DF6-9D61-2D7F2C9F54A0}, 58, D:\work\shadow\test.pdb 67CF0DA2 feat 14 00076360 75360 Counts: Pre-VC++ 11.00=0, C/C++=205, /GS=205, /sdl=0, guardN=204 67CF0DA2 coffgrp 36C 00076374 75374 67CF0DA2 deopt 22 00076708 75708 Timestamp: 0x67cf0da2, size: 532480, name: test.alt.exe
deopt
デバッグ ディレクトリ エントリが存在しない場合は、/dynamicdeopt
、cl.exe
、およびlib.exe
にlink.exe
を渡していることを確認します。すべての
/dynamicdeopt
、cl.exe
、バイナリ ファイルのlib.exe
、link.exe
、および.cpp
に.lib
が渡されない場合、動的な最適化は一貫して機能しません。 プロジェクトをビルドするときに、適切なスイッチが設定されていることを確認します。
既知の問題の詳細については、「 C++ 動的デバッグ: 最適化されたビルドの完全なデバッグ機能」を参照してください。
想定どおりに動作しない場合は、 開発者コミュニティでチケットを開きます。 問題に関するできるだけ多くの情報を含めます。
一般的な注意事項
IncrediBuild 10.24 では、C++ 動的デバッグ ビルドがサポートされています。
FastBuild v1.15 では、C++ 動的デバッグ ビルドがサポートされています。
インライン化された関数は、オンデマンドで最適化されません。 インライン関数にブレークポイントを設定すると、デバッガーは関数とその呼び出し元を最適化解除します。 ブレークポイントは、コンパイラの最適化なしでプログラムがビルドされたかのように、予期した場所にヒットします。
関数内のブレークポイントを無効にしても、関数は最適化されません。 関数のブレークポイントを削除して、最適化された状態に戻す必要があります。
多くの動的デバッグ ブレークポイントは、最適化されたバイナリと最適化されていないバイナリの 2 つのブレークポイントです。 このため、[ブレークポイント] ウィンドウに複数のブレークポイント が 表示されます。
最適化されていないバージョンに使用されるコンパイラ フラグは、最適化されたバージョンに使用されるフラグと同じですが、最適化フラグと /dynamicdeopt
を除きます。 この動作は、マクロなどを定義するように設定したフラグも、最適化されていないバージョンで設定されることを意味します。
最適化されていないコードは、デバッグ コードと同じではありません。 最適化されていないコードは最適化されたバージョンと同じ最適化フラグでコンパイルされるため、デバッグ固有の設定に依存するアサートやその他のコードは含まれません。
ビルド システム統合
C++ 動的デバッグでは、コンパイラフラグとリンカー フラグを特定の方法で設定する必要があります。 以降のセクションでは、競合するスイッチがない動的デバッグ専用の構成を設定する方法について説明します。
Visual Studio ビルド システムを使用してプロジェクトをビルドする場合、動的デバッグ構成を作成する適切な方法は、Configuration Manager を使用してリリースまたはデバッグの構成を複製し、動的デバッグに対応するように変更することです。 以降の 2 つのセクションでは、手順について説明します。
新しいリリース構成を作成する
Visual Studio のメイン メニューで、[ ビルド>構成マネージャー ] を選択して Configuration Manager を開きます。
[ 構成 ] ドロップダウン リストを選択し、 <New...> を選択します。
Configuration Manager の [プロジェクト コンテキスト] の [構成] ドロップダウン リストが開き、
が強調表示されています。 [ 新しいソリューション構成] ダイアログが開きます。 [ 名前 ] フィールドに、新しい構成の名前 (
ReleaseDD
など) を入力します。 [設定のコピー元] が [リリース] に設定されていることを確認します。 次に、[ OK] を 選択して新しい構成を作成します。[名前] フィールドは ReleaseDD に設定されます。 [設定のコピー元] ドロップダウン リストが [リリース] に設定されています。
新しい構成が [ アクティブ なソリューション構成 ] ドロップダウン リストに表示されます。 を選択してを閉じます。
[構成] ドロップダウン リストが ReleaseDD に設定されている状態で、ソリューション エクスプローラーでプロジェクトを右クリックし、[プロパティ] を選択します。
[構成プロパティ>Advanced] で、[C++ 動的デバッグの使用] を [はい] に設定します。
プログラム全体の最適化が [いいえ] に設定されていることを確認します。
プロパティ ページが開き、[構成プロパティ] > [詳細設定] が表示されます。 C++ 動的デバッグを使用します。 プロパティは [はい] に設定されています。 [プログラム全体の最適化] が [いいえ] に設定されています。
[構成プロパティ>Linker>最適化] で、[COMDAT フォールディングを有効にする] が [いいえ] (/OPT:NOICF) に設定されていることを確認します。
プロパティ ページを開き、[構成プロパティ] > [リンカー] > [最適化] > CMDAT フォールディングを有効にします。 このプロパティは No (/OPT:NOICF) に設定されています。
この設定により、 /dynamicdeopt
スイッチがコンパイラとリンカーに追加されます。 C++ 最適化スイッチが /GL
され、 /OPT:ICF
もオフになっているので、C++ 動的デバッグで使用できる最適化されたリリース ビルドが必要な場合に、新しい構成でプロジェクトをビルドして実行できるようになりました。
リテール ビルドで使用するその他のスイッチをこの構成に追加して、動的デバッグを使用するときに予期されるスイッチを常にオンまたはオフにすることができます。 動的デバッグで使用すべきではないスイッチの詳細については、「 互換性のないオプション」を参照してください。
Visual Studio での構成の詳細については、「 構成の作成と編集」を参照してください。
新しいデバッグ構成を作成する
デバッグ バイナリを使用するが、より高速に実行する場合は、デバッグ構成を変更できます。
Visual Studio のメイン メニューで、[ ビルド>構成マネージャー ] を選択して Configuration Manager を開きます。
[ 構成 ] ドロップダウン リストを選択し、 <New...> を選択します。
Configuration Manager のウィンドウの [プロジェクト コンテキスト] 部分で、[構成] ドロップダウン リストが開き、
が強調表示されます。 [ 新しいプロジェクト構成] ダイアログが開きます。 [ 名前 ] フィールドに、 DebugDD などの新しい構成の名前を入力します。 コピー元設定:がデバッグに設定されていることを確認します。 次に、[ OK] を 選択して新しい構成を作成します。
名前フィールドは DebugDD に設定されます。 設定をコピーするドロップダウンリストは「デバッグ」に設定されています。
新しい構成が [ アクティブ なソリューション構成 ] ドロップダウン リストに表示されます。 を選択してを閉じます。
[構成] ドロップダウン リストが DebugDD に設定されている状態で、ソリューション エクスプローラーでプロジェクトを右クリックし、[プロパティ] を選択します。
構成プロパティ>C/C++>最適化で、必要な最適化を有効にします。 たとえば、 最適化 を 最大速度 (/O2) に設定できます。
C/C++>Code の生成で、基本ランタイム チェックを既定値に設定します。
C/C++>General で、マイ コード デバッグのみをサポートすることを無効にします。
デバッグ情報の形式を Program Database (/Zi) に設定します。
デバッグ ビルドで使用する他のスイッチをこの構成に追加して、動的デバッグの使用時に期待どおりのスイッチを常にオンまたはオフにすることができます。 動的デバッグで使用すべきではないスイッチの詳細については、「 互換性のないオプション」を参照してください。
Visual Studio での構成の詳細については、「 構成の作成と編集」を参照してください。
カスタム ビルド システムに関する考慮事項
カスタム ビルド システムがある場合は、次のことを確認します。
ビルド ディストリビューターの場合:
-
test
という名前のプロジェクトの場合、コンパイラはtest.alt.obj
、test.alt.exp
、test.obj
、およびtest.exp
を生成します。 リンカーは、test.alt.exe
、test.alt.pdb
、test.exe
、およびtest.pdb
を生成します。 -
c2dd.dll
と共に新しいツールセット バイナリc2.dll
をデプロイする必要があります。
互換性のないオプション
一部のコンパイラおよびリンカー オプションは、C++ 動的デバッグと互換性がありません。 Visual Studio プロジェクト設定を使用して C++ 動的デバッグを有効にした場合、互換性のないオプションは、追加のコマンド ライン オプション設定で明示的に設定しない限り、自動的にオフになります。
次のコンパイラ オプションは、C++ 動的デバッグと互換性がありません。
/GH
/GL
/Gh
/RTC1
/RTCc
/RTCs
/RTCu
/ZI (/Zi is OK)
/ZW
/clr
/clr:initialAppDomain
/clr:netcore
/clr:newSyntax
/clr:noAssembly
/clr:pure
/clr:safe
/fastcap
/fsanitize=address
/fsanitize=kernel-address
次のリンカー オプションは、C++ 動的デバッグと互換性がありません。
/DEBUG:FASTLINK
/INCREMENTAL
/OPT:ICF You can specify /OPT:ICF but the debugging experience may be poor
こちらも参照ください
/dynamicdeopt コンパイラ フラグ (プレビュー)
/DYNAMICDEOPT リンカー フラグ (プレビュー)
C++ 動的デバッグ: 最適化されたビルドの完全なデバッグ可能性
最適化されたコードをデバッグする