Выжившая версия выпуска дает хороший обзор.
Вещи, с которыми я столкнулся - большинство уже упоминалось
Инициализация переменной
безусловно, самый распространенный. В Visual Studio отладочные сборки явно инициализируют выделенную память для заданных значений, см., Например, Значения памяти здесь. Эти значения обычно легко обнаружить, они вызывают ошибку «вне границ» при использовании в качестве индекса или нарушение доступа при использовании в качестве указателя. Однако неинициализированное логическое значение истинно и может привести к тому, что ошибки неинициализированной памяти останутся незамеченными в течение многих лет.
В выпусках Release, где память явно не инициализирована, она просто сохраняет содержимое, которое было раньше. Это приводит к «смешным значениям» и «случайным» сбоям, но также часто к детерминированным сбоям, которые требуют выполнения явно не связанной команды до того, как команда действительно произойдет сбой. Это вызвано тем, что первая команда «устанавливает» ячейку памяти с определенными значениями, а когда ячейки памяти перезагружаются, вторая команда видит их как инициализации. Это чаще встречается с неинициализированными переменными стека, чем с кучей, но последнее также случилось со мной.
Инициализация необработанной памяти также может отличаться в сборке выпуска независимо от того, запускаете ли вы из Visual Studio (с отладчиком) и из обозревателя. Это делает «самые приятные» ошибки сборки релизов, которые никогда не появляются под отладчиком.
Действительные оптимизации на втором месте в моем опыте. Стандарт C ++ позволяет проводить много оптимизаций, что может быть удивительно, но вполне допустимо, например. когда два указателя имеют псевдоним в одной и той же ячейке памяти, порядок инициализации не учитывается или несколько потоков изменяют одну и ту же ячейку памяти, и вы ожидаете определенного порядка, в котором поток B видит изменения, сделанные потоком A. Часто компилятор обвиняется в эти. Не так быстро, молодой Йеди! - см. ниже
Сроки Выпуски сборки не просто "работают быстрее" по разным причинам (оптимизация, функции ведения журнала, обеспечивающие точку синхронизации потока, отладочный код, такой как утверждения не выполняются и т. Д.), А также относительное время между операциями резко меняются. Наиболее распространенная проблема, обнаруженная этим, - это состояние гонки, а также взаимоблокировки и простое выполнение кода, основанного на сообщениях, таймерах и событиях. Несмотря на то, что они имеют проблемы с синхронизацией, они могут быть на удивление стабильными во всех сборках и платформах с воспроизведением, которое «работает всегда, кроме ПК 23».
Байт охраны . Отладочные сборки часто помещают (больше) защитные байты вокруг выбранных экземпляров и распределений, чтобы защитить от переполнения индекса и иногда от переполнения. В тех редких случаях, когда код опирается на смещения или размеры, например, сериализуя необработанные структуры, они разные.
Другие различия в коде Некоторые инструкции, например, утверждают, ничего не дают в сборках релиза. Иногда они имеют разные побочные эффекты. Это распространено в макросе, как в классическом (предупреждение: многократные ошибки)
#ifdef DEBUG
#define Log(x) cout << #x << x << "\n";
#else
#define Log(x)
#endif
if (foo)
Log(x)
if (bar)
Run();
Что в сборке выпуска оценивается в if (foo && bar)
Этот тип ошибки очень редко встречается в обычном коде C / C ++ и правильно написанных макросах.
Ошибки компилятора Такого никогда не бывает. Что ж, это так, но вам по большей части лучше, если вы этого не сделаете. За десятилетие работы с VC6 я нашел одну, в которой все еще убежден, что это нефиксированная ошибка компилятора по сравнению с десятками шаблонов (возможно, даже сотнями примеров) с недостаточным пониманием Священного Писания (a.k.a. стандарт).