Лучшие практики и инструменты для отладки различий между сборками Debug и Release?

Я видел сообщения о том, что может вызвать различия между сборками Debug и Release, но я не думаю, что кто-то обратился с точки зрения разработки, что является наиболее эффективным способом решения проблемы.

Первое, что я делаю, когда ошибка появляется в сборке Release, но не в Debug, я запускаю свою программу через valgrind в надежде на лучший анализ. Если это ничего не показывает, - и это случилось со мной раньше, - тогда я пробую различные материалы в надежде получить ошибку и на поверхности также в сборке Debug. Если это не удастся, я попытаюсь отслеживать изменения, чтобы найти самую последнюю версию, для которой две сборки расходятся в поведении. И, наконец, я предполагаю, что я прибегаю к заявлениям печати. ​​

Существуют ли лучшие методы разработки программного обеспечения для эффективной отладки, когда отличия от сборки Debug и Release отличаются? Кроме того, какие инструменты существуют на более фундаментальном уровне, чем valgrind, чтобы помочь отладить эти случаи?

EDIT: я замечаю много ответов, предлагающих некоторые общие хорошие практики, такие как модульное тестирование и регрессионное тестирование, что я согласен, отлично подходит для поиска любой ошибки. Однако есть ли что-то, специально адаптированное к этой проблеме Release and Debug? Например, существует ли такая вещь, как инструмент статического анализа, который гласит: "Эй, этот макрос или этот код или эта практика программирования опасна, потому что она может вызвать различия между вашими сборками Debug/Release?"

Ответ 1

Само существование двух конфигураций является проблемой с точки зрения отладки. Правильное проектирование было бы таким, чтобы система на земле и в воздухе вела себя одинаково и достигла этого, уменьшив количество способов, которыми система может отличить разницу.

Отладочные и выпускные сборки отличаются тремя аспектами:

  • _DEBUG define
  • Оптимизация
  • другая версия стандартной библиотеки

Лучший способ, как я часто работаю, это:

  • Отключить оптимизацию, когда производительность не критична. Отладка важнее. Самое главное - отключить функцию автоинкремента, сохранить стандартный стек кадров и оптимизировать повторное использование переменных. Они больше всего отлаживают отладки.
  • Определить код монитора зависимости от DEBUG. Никогда не используйте утверждения только для отладки или любые другие инструменты, чувствительные к определению DEBUG.
  • По умолчанию компилировать и работать/выпускать.

Ответ 2

Еще одна "Лучшая практика", или, скорее, комбинация из двух: иметь автоматические тесты единиц и делиться и побеждать.

Если у вас есть модульное приложение, и каждый модуль имеет хорошие модульные тесты, вы можете быстро изолировать ошибочную деталь.

Ответ 3

Когда я сталкиваюсь с ошибкой, которая возникает только в релизе, первое, что я всегда ищу, это использование неинициализированной переменной стека в коде, над которым я работаю. В Windows среда выполнения отладки C автоматически инициализирует переменные стека с помощью бита знакового бита, 0xcdcdcdcd или что-то в этом роде. В выпуске переменные стека будут содержать значение, которое в последний раз хранилось в этой ячейке памяти, что будет неожиданным значением.

Во-вторых, я попытаюсь определить, что отличает между сборками debug и release. Я смотрю настройки оптимизации компилятора, которые передаются компилятором в конфигурациях Debug и Release. Вы можете увидеть, что это последняя страница свойств параметров компилятора в Visual Studio. Я начну с конфигурации release и изменю аргументы командной строки, переданные компилятору на один элемент за раз, пока они не совпадут с командной строкой, которая используется для компиляции при отладке. После каждого изменения я запускаю программу и воспроизвожу ошибку. Это часто приводит меня к определенному параметру, который вызывает ошибку.

Третий метод может состоять в том, чтобы использовать функцию, которая неправильно управляет и отключает оптимизацию вокруг нее с использованием препроцессора. Это позволит вам запустить программу в выпуске с конкретной функцией, скомпилированной в debug. Поведение программы, которая была построена таким образом, поможет вам узнать больше об ошибке.

#pragma optimize( "", off )
void foo() {
    return 1;
}
#pragma optimize( "", on ) 

По опыту, как правило, проблемы обычно представляют собой инициализацию стека, очистку памяти в распределителе памяти или странные директивы #define, приводящие к некорректному компиляции кода.

Ответ 4

Наиболее очевидной причиной является просто использование директив #ifdef и #ifndef, связанных с DEBUG или аналогичным символом, который изменяется между двумя сборками.

Перед тем как спуститься по отладочной дороге (это не моя личная идея), я бы проверил обе командные строки и проверил, какие флаги передаются в одном режиме, а не в другом, а затем grep мой код для этих флагов и проверьте их использование.

Одна особая проблема, которая приходит на ум, - это макросы:

#ifdef _DEBUG_
  #define CHECK(CheckSymbol) { if (!(CheckSymbol)) throw CheckException(); }
#else
  #define CHECK(CheckSymbol)
#endif

также известен как soft-assert.

Ну, если вы вызываете его с функцией, которая имеет побочный эффект, или полагаться на нее, чтобы охранять функцию (принудительное исполнение контракта) и каким-то образом улавливает исключение, она бросает в отладку и игнорирует ее... вы увидите различия в выпуске:)

Ответ 5

Когда отладка и разблокировка различаются, это означает:

  • ваш код зависит от _DEBUG или подобных макросов (определенных при компиляции отладочной версии - без оптимизации)
  • ваш компилятор имеет ошибку оптимизации (я видел это несколько раз)

Вы можете легко справиться с (1) (модификация кода), но с (2) вам придется изолировать ошибку компилятора. После выделения ошибки вы немного "переписываете код", чтобы заставить компилятор генерировать правильный двоичный код (я делал это несколько раз - самая сложная часть - изолировать ошибку).

Я могу сказать, что при включении отладочной информации для версии выпуска процесс отладки работает... (хотя из-за оптимизации вы можете увидеть некоторые "странные" прыжки при запуске).

Для вашего приложения вам понадобятся некоторые тесты "черного ящика" - valgrind - это решение в этом случае. Эти решения помогут вам найти различия между выпуском и отладкой (что важно очень).

Ответ 6

Лучшим решением является создание чего-то вроде автоматизированного модульного тестирования для тщательного тестирования всех аспектов приложения (не только отдельных компонентов, но и реальных тестов, которые используют приложение так же, как обычный пользователь со всеми зависимостями), Это позволяет сразу узнать, когда была введена ошибка только для выпуска, которая должна дать вам представление о том, где проблема.

Хорошая практика для активного мониторинга и поиска проблем превосходит любой инструмент, который поможет вам исправить их задолго до того, как они произойдут.

Однако, когда у вас есть один из тех случаев, когда он слишком поздно: слишком много сборок прошло, не может последовательно воспроизводиться и т.д., тогда я не знаю ни одного инструмента для работы. Иногда возиться с настройками релиза может дать представление о том, почему возникает ошибка: если вы можете устранить оптимизацию, которая внезапно заставит ошибку исчезнуть, это может дать вам полезную информацию об этом.

Ошибки только релиза могут попадать в разные категории, но наиболее распространенными (помимо чего-то вроде неправильного использования утверждений) является:

1) Неинициализированная память. Я использую этот термин для неинициализированных переменных, поскольку переменная может быть инициализирована, но все же указывать на память, которая не была инициализирована должным образом. Для этого могут помочь инструменты диагностики памяти, такие как Valgrind.

2) Сроки (например: условия гонки). Это может быть кошмар для отладки, но есть некоторые многопоточные профилировщики и диагностические инструменты, которые могут помочь. Я не могу предложить никого с места в карьер, но в качестве примера показан Coverity Integrity Manager.