Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Это важно
Динамическая отладка C++ в настоящее время доступна в предварительной версии. Эта информация относится к предварительной версии функции, которая может быть существенно изменена до выпуска. Корпорация Майкрософт не предоставляет никаких гарантий, выраженных или подразумеваемых, в отношении информации, предоставленной здесь.
Эта предварительная версия функции, доступная начиная с Visual Studio 2022 версии 17.14(предварительная версия 2), применяется только к проектам x64.
С помощью динамической отладки C++ можно отлаживать оптимизированный код, как если бы он не был неоптимизирован. Эта функция полезна для разработчиков, которым требуются преимущества производительности оптимизированного кода, например разработчики игр, которым требуются высокие частоты кадров. Благодаря динамическим возможностям отладки в C++ вы можете наслаждаться процессом отладки неоптимизированного кода, не жертвуя преимуществами производительности оптимизированных сборок.
Отладка оптимизированного кода вызывает проблемы. Компилятор изменяет положение и переорганизует инструкции по оптимизации кода. Результатом является более эффективный код, но это означает:
- Оптимизатор может удалить локальные переменные или переместить их в расположения, неизвестные отладчику.
- Код внутри функции больше не соответствует исходному коду, если оптимизатор объединяет блоки кода.
- Имена функций в стеке вызовов могут быть неправильными, если оптимизатор объединяет две функции.
В прошлом разработчики сталкивались с этими проблемами и другими, когда они находились в процессе отладки оптимизированного кода. Теперь эти проблемы устранены, так как при динамической отладке C++ можно перейти в оптимизированный код, как если бы он не был неоптимизирован.
Помимо создания оптимизированных двоичных файлов, компиляция с /dynamicdeopt
также генерирует неоптимизированные двоичные файлы, которые используются во время отладки. При добавлении точки останова или входе в функцию (включая функции __forceinline
), отладчик загружает неоптимизированный двоичный файл. Затем можно отлаживать неоптимизованный код для функции вместо оптимизированного кода. Вы можете отлаживать код так же, как отлаживаете неоптимизированный код, при этом продолжая получать преимущества оптимизированного кода в остальной части программы.
Попробуйте динамическую отладку C++
Во-первых, давайте рассмотрим, что такое отладка оптимизированного кода. Затем вы увидите, как динамическое отладка C++ упрощает процесс.
Создайте проект консольного приложения C++ в Visual Studio. Замените содержимое файла 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.
Выберите Построить>Перестроить решение.
Установите точку останова на строке 55,
int neighbors = countNeighbors(grid, i, j);
вupdateGrid()
. Запустите программу.При достижении точки останова откройте окно Локальные. В главном меню выберите "Отладка>локальных параметров>". Обратите внимание, что в окне
i
вы не можете увидеть значениеj
или . Компилятор устранил их благодаря оптимизации.Попробуйте установить точку останова на строке 19, в
cout << (grid[i][j] ? '*' : ' ');
printGrid()
. Это невозможно. Это поведение ожидается, потому что компилятор оптимизировал код.
Остановите программу и включите динамическую отладку C++ и повторите попытку.
В обозревателе решений щелкните проект правой кнопкой мыши и выберите "Свойства ", чтобы открыть страницы свойств проекта.
Выберите "Дополнительное>использование динамической отладки C++" и измените значение "Да".
Откроется страница свойств > Свойства конфигурации > Дополнительно > Использование динамической отладки C++. Для свойства задано значение Yes.
Этот шаг добавляет ключ
/dynamicdeopt
к компилятору и линкеру. За кулисами он также отключает переключатели/GL
и/OPT:ICF
для оптимизации C++. Этот параметр не перезаписывает переключатели, добавленные вручную в командную строку или другие параметры оптимизации, которые задаются, например/O1
.Выберите Построить>Перестроить решение. Появится диагностический код
MSB8088
сборки, указывающий на несовместимость динамической отладки и оптимизации всей программы. Эта ошибка означает, что во время компиляции вся оптимизация/GL
программы () была автоматически отключена.Вы можете вручную отключить всю оптимизацию программы в свойствах проекта. Выберите "Свойства> конфигурации" Расширенная>оптимизация всей программы" и измените значение "Отключить". Теперь
MSB8088
он рассматривается как предупреждение, но он может рассматриваться как ошибка в будущей версии Visual Studio.Повторно запустите приложение.
Теперь, когда вы попали в точку останова в строке 55, вы увидите значения
i
иj
в окне Локальные. В окне стека вызовов показано, чтоupdateGrid()
неоптимизировано, а имя файла —life.alt.exe
. Этот альтернативный двоичный файл используется для отладки оптимизированного кода.Точка останова отображается в функции updateGrid. Стек вызовов показывает, что функция неоптимизирована, а имя файла — life.alt.exe. В окне "Локальные" отображаются значения i и j и другие локальные переменные в функции.
Функция
updateGrid()
неоптимизирована по запросу, так как в ней устанавливается точка останова. При переходе по оптимизированной функции во время отладки она не будет отключена. Если вы входите в функцию, она теряет оптимизацию. Основной способ деоптимизации функции — установить в ней точку останова или войти в нее.Вы также можете деоптимизировать функцию в окне стека вызовов. Щелкните правой кнопкой мыши функцию или выбранную группу функций и выберите Deoptimize для следующей записи. Эта функция полезна, если вы хотите просмотреть локальные переменные в оптимизированной функции, для которой точка останова не была установлена в другом месте стека вызовов. Функции, которые неоптимизированы таким образом, группируются в окне точек останова в виде группы точек останова с именем Deoptimized Functions. Если удалить группу точек останова, связанные функции будут возвращены в оптимизированное состояние.
Использование условных и зависимых точек останова
Повторите настройку точки останова в строке 19.
cout << (grid[i][j] ? '*' : ' ');
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 выберите пункт Инструменты>Параметры>Отладка>Общие. Снимите флажок "Автоматическая отмена оптимизации отлаживаемых функций при возможности (.NET 8+, динамическая отладка C++)." При следующем запуске отладчика код остается оптимизированным.
- Многие точки останова динамической отладки — это две точки останова: одна в оптимизированном двоичном файле и одна в неоптимизованном двоичном файле. В окне Остановки выберите Показать столбцы>Функция. Снимите точку останова, связанную с бинарным файлом
alt
. Другая точка останова в паре прерывается в оптимизированном коде. - При отладке в главном меню Visual Studio выберите "Отладка>Windows>Дизассембли". Убедитесь, что на нем сосредоточено внимание. При переходе в функцию через окно Disassembly функция не будет деоптимизирована.
- Полностью отключите динамическую отладку, не передавая
/dynamicdeopt
вcl.exe
,lib.exe
иlink.exe
. Если вы используете сторонние библиотеки и не можете их перестроить, не добавляйте/dynamicdeopt
во время окончательногоlink.exe
для отключения динамической отладки данного двоичного файла. - Чтобы быстро отключить динамическую отладку для одного двоичного файла (например,
test.dll
), переименуйте или удалите двоичныйalt
файл (например,test.alt.dll
). - Чтобы отключить динамическую отладку для одного или нескольких
.cpp
файлов, не передайте/dynamicdeopt
их при сборке. Оставшаяся часть проекта создается с помощью динамической отладки.
Включение динамической отладки C++ в Unreal Engine
Unreal Engine 5.6 поддерживает динамическую отладку C++ как для средства сборки Unreal, так и для акселератора Unreal Build. Существует два способа его включения:
Измените
Target.cs
файл проекта, чтобы он содержалсяWindowsPlatform.bDynamicDebugging = true
.Используйте конфигурацию редактора разработки и измените
BuildConfiguration.xml
, чтобы включить:<WindowsPlatform> <bDynamicDebugging>true</bDynamicDebugging> </WindowsPlatform>
Для Unreal Engine 5.5 или более ранних версий, перенесите изменения средства Unreal Build Tool из GitHub в ваш репозиторий. Затем включите bDynamicDebugging
, как указано выше. Кроме того, необходимо использовать ускоритель сборки Unreal из Unreal Engine 5.6. Используйте последние биты из ue5-main или отключите UBA, добавив следующее:BuildConfiguration.xml
<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
, предназначенного для деоптимизированной отладки. Откройте командную строку Visual Studio с поддержкой x64 и выполните команду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
.Динамическая деоптимизация не будет работать согласованно, если
/dynamicdeopt
не передается вcl.exe
,lib.exe
иlink.exe
для всех.cpp
,.lib
и двоичных файлов. Убедитесь, что при сборке проекта заданы правильные параметры.
Дополнительные сведения о известных проблемах см. в C++ Dynamic Debugging: Full Debuggability for Optimized Builds.
Если что-то не работает должным образом, отправьте запрос в сообществе разработчиков. Добавьте максимально подробную информацию о проблеме.
Общие примечания
IncrediBuild 10.24 поддерживает сборки динамической отладки C++.
FastBuild версии 1.15 поддерживает сборки динамической отладки C++.
Встраиваемые функции деоптимизируются по требованию. Если вы задаете точку останова для встроенной функции, отладчик дезоптимизирует эту функцию и вызывающую её функцию. Точка останова срабатывает там, где вы ожидаете, как будто ваша программа была создана без оптимизаций компилятора.
Функция остается деоптимизированной, даже если вы отключите точки останова в ней. Чтобы восстановить оптимизированное состояние, необходимо удалить точку останова функции.
Многие точки останова динамической отладки — это две точки останова: одна в оптимизированном двоичном файле и одна в неоптимизованном двоичном файле. По этой причине вы видите более одной точки останова в окне Breakpoints.
Флаги компилятора, используемые для неоптимизированной версии, совпадают с флагами, используемыми для оптимизированной версии, за исключением флагов оптимизации и /dynamicdeopt
. Это означает, что все флаги, заданные для определения макросов, и т. д., также задаются в неоптимизированной версии.
Неоптимизованный код не совпадает с отладочным кодом. Неоптимизованный код компилируется с теми же флагами оптимизации, что и оптимизированная версия, поэтому утверждения и другой код, основанный на параметрах отладки, не включаются.
Интеграция систем
Для динамической отладки C++ необходимо задать флаги компилятора и компоновщика определенным образом. В следующих разделах описывается настройка выделенной конфигурации для динамической отладки, которая не имеет конфликтующих коммутаторов.
Если ваш проект собран с использованием системы сборки Visual Studio, хорошим способом создать конфигурацию для динамической отладки является использование Configuration Manager для клонирования конфигурации Release или Debug и внесения изменений для адаптации к динамической отладке. В следующих двух разделах описаны процедуры.
Создать конфигурацию релиза
В главном меню Visual Studio выберите «Сборка»>Configuration Manager, чтобы открыть Configuration Manager.
Выберите раскрывающийся список конфигурации и нажмите кнопку <"Создать...>".
В Configuration Manager в контекстах проекта выпадающий список конфигурации открыт и
выделен. Откроется диалоговое окно "Новая конфигурация решения ". В поле "Имя" введите имя новой конфигурации, например
ReleaseDD
. Убедитесь, что параметр Копировать настройки из: установлен на Release. Затем нажмите кнопку "ОК ", чтобы создать новую конфигурацию.Поле "Имя" имеет значение ReleaseDD. Раскрывающийся список "Копировать параметры из" имеет значение Release.
Новая конфигурация появится в раскрывающемся списке активных конфигураций решения . Выберите Закрыть.
Если в раскрывающемся списке конфигурации задано значение ReleaseDD, щелкните проект правой кнопкой мыши в обозревателе решений и выберите "Свойства".
В "Свойства конфигурации">расширенные установите динамическую отладку C++ в положение Да.
Убедитесь, что для всей оптимизации программы задано значение No.
Cтраница свойств открывается на Конфигурация > Advanced. Используйте динамическую отладку C++ . Для свойства задано значение Yes. Цельная оптимизация программы установлена в Нет.
В разделе Свойства конфигурации>Компоновщик>Оптимизация убедитесь, что ключ свертывания COMDAT установлен в положение «Нет» (/OPT:NOICF).
Страница свойств открывается на Конфигурация свойств > Компоновщик > Оптимизация > Включить CMDAT Folding. Свойство установлено в значение Нет (/OPT:NOICF).
Этот параметр добавляет переключатель /dynamicdeopt
к компилятору и компоновщику. С учетом того, что коммутаторы оптимизации C++ /GL
и /OPT:ICF
тоже выключены, теперь можно создать и запустить проект в новой конфигурации, если требуется оптимизированная сборка релиза, которую можно использовать с динамической отладкой C++.
В эту конфигурацию можно добавить другие параметры, которые вы используете с розничными сборками, чтобы точно были включены или отключены те параметры, которые вы ожидаете при использовании динамической отладки. Дополнительные сведения о параметрах, которые не следует использовать с динамической отладкой, см. в разделе несовместимые параметры.
Дополнительные сведения о конфигурациях в Visual Studio см. в статье "Создание и изменение конфигураций".
Создание конфигурации отладки
Если вы хотите использовать двоичные файлы отладки, но хотите, чтобы они выполнялись быстрее, можно изменить конфигурацию отладки.
В главном меню Visual Studio выберите «Сборка»>Configuration Manager, чтобы открыть Configuration Manager.
Выберите раскрывающийся список конфигурации и нажмите кнопку <"Создать...>".
В диспетчере конфигураций в части окна, относящейся к контекстам проекта, раскрывающийся список конфигураций открыт и
выделен. Откроется диалоговое окно "Новая конфигурация проекта ". В поле "Имя" введите имя новой конфигурации, например DebugDD. Убедитесь, что Копировать параметры из: установлено на Debug. Затем нажмите кнопку "ОК ", чтобы создать новую конфигурацию.
Поле имени имеет значение DebugDD. В раскрывающемся списке "Скопировать параметры из:" выбрано "Отладка".
Новая конфигурация появится в раскрывающемся списке активных конфигураций решения . Выберите Закрыть.
В раскрывающемся списке "Конфигурация" установите значение DebugDD, щелкните проект правой кнопкой мыши в обозревателе решений и выберите "Свойства".
В Свойствах конфигурации>C/C++>Оптимизация включите нужные оптимизации. Например, можно установить оптимизацию на максимальную скорость (/O2).
В C/C++>Генерации кода установите Базовые проверки среды выполнения на По умолчанию.
В C/C++>General отключите поддержку отладки только моего кода.
Установите формат отладочной информации на Program Database (/Zi).
В эту конфигурацию можно добавить другие коммутаторы, которые используются при отладке сборок, чтобы вы всегда имели именно включенные или отключенные параметры при использовании динамической отладки. Дополнительные сведения о параметрах, которые не следует использовать с динамической отладкой, см. в разделе несовместимые параметры.
Дополнительные сведения о конфигурациях в Visual Studio см. в статье "Создание и изменение конфигураций".
Аспекты настраиваемой системы сборки
Если у вас есть пользовательская система сборки, убедитесь, что вы:
- Передайте
/dynamicdeopt
вcl.exe
,lib.exe
иlink.exe
. - Не используйте
/ZI
, любой из/RTC
флагов и/JMC
.
Для дистрибьюторов сборок:
- Для проекта с именем
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++. Если включить динамическую отладку C++ с помощью параметров проекта Visual Studio, несовместимые параметры автоматически отключаются, если они не заданы в дополнительных параметрах командной строки.
Следующие параметры компилятора несовместимы с динамической отладкой 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++ : полная отладка для оптимизированных сборок
Отладочный оптимизированный код