Чем debug отличается от release с

от admin

[Visual Studio] — Differences between Debug and Release

OK, your program works. You’ve tested everything in sight. It’s time to ship it. So you make a Release version.

Oops, your world crumbles to dust and you don’t know how to debug it in the Release version. so, we have to figure out the reasons.

Release vs Debug

Essentially, they’re just 2 separate configurations of the compiler. and it depends on what language you are using, Debug includes debugging information in the compiled files (allowing easy debugging) while Release usually has optimizations enabled. They each define different symbols that can be checked in your program, but they are language-specific macros.

Debug

    : Use run-time library for Debug : Disable optimization
  • /D “_DEBUG”: Equals to #define _DEBUG, open code like assert: Open Edit and Continue database, allows us to modify code while debugging : Enable stack frame Run-Time error checking. deprecated. use /RTC instead.

Release

    : Use run-time library for Release : Minimize size : Maximize speed
  • /D “NDEBUG”: Close conditional compilation (don’t compile assert) : Eliminate duplicate strings and pools strings as read-only. If you try to modify strings under /GF, an application error occurs.

Errors we may encounter in these 2 modes:

Variables Errors (/GZ)

As we know that Debug and Release do different things when initializing variables, Debug marks the memory to 0xcc for breakpoints purpose while Release randomly allocates them in the heap. So if your variable is not initialized and it should work fine in Debug mode but it may cause some random problems in Release mode.

Runtime Library Errors (/MD /MDd)

The run-time library that provided by compilers usually are stable in Release mode, but when we enable run-time Library for Debug which contains some debugging information and protection mechanism to help to find errors, the performance is worse than Release mode. Since it does more checks in Debug Runtime library such as memory allocation…etc. Sometimes we encounter errors in Debug but work fine in Release.

ASSERT, VERIFY, TRACE…Macro (DEBUG and NDEBUG)

Sometimes if we don’t handle these macro well, it should fail in Debug mode, but work in Release mode.

Optimization impact (/O1 and /O2)

When we implement methods, all messages including return address, passing arguments and other parameters are stored in stacks. If the implementations don’t match declarations, they cause errors while compiling, but in Debug mode, your every access of stacks are stored in EBP(Extended Base Pointer) which is always pointing to the bottom(base) of the stacks and it should work fine. But in Release mode, EBP will be optimized, and it may break sometimes.

Пара историй про отличия Release от Debug

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

История 1

Собственно, началось все с того, что пришел баг о том что при некоторых операциях приложение вылетает. Это бывает часто. Баг не захотел воспроизводиться в Debug-версии. Это порой бывает. Поскольку в приложении часть библиотек была написано на C++, то первой мыслью было что-то вроде «где-то забыли переменную проинициализировать или что-то в этом духе». Но на деле суть бага крылась в управляемом коде, хотя без неуправляемого тоже не обошлось.

А код оказался примерно следующим:

public Wrapper()
<
this .Obj = CreateUnmObject();
>

Wrapper()
<
this .Dispose( false );
>

protected virtual void Dispose( bool disposing)
<
if (disposing)
<
>

this .ReleaseUnmObject( this .Obj);
this .Obj = IntPtr .Zero;
>

public void Dispose()
<
this .Dispose( true );
GC.SuppressFinalize( this );
>
>

* This source code was highlighted with Source Code Highlighter .

В принципе, практически каноническая реализация шаблона IDisposable («практически» — потому, что нет переменной disposed, вместо нее обнуление указателя), вполне стандартный класс-обертка неуправляемого ресурса.

Использовался же класс примерно следующим образом:

* This source code was highlighted with Source Code Highlighter .

Естественно, что внимательный читатель сразу обратит внимание, что объекта wr надо вызвать Dispose, то есть обернуть все конструкцией using. Но на первый взгляд, на причину падения это не должно повлиять, так как разница будет в том детерминировано ли очистится ресурс или нет.

Но на самом деле разница есть и именно в релизной сборке. Дело в том, что объект wr становится доступным сборщику мусора сразу после начала выполнения метода DoCalculations, ведь больше нет ни одного «живого» объекта, кто на него ссылался бы. А значит wr вполне может(а так оно и происходило) быть уничтожен во время выполнения DoCalculations и указатель, переданный в этот метод становится невалидным.

Если обернуть вызов DoCalculations в using (Wrapper wr = new Wrapper())<. >, то это решит проблему, поскольку вызов Dispose в блоке finally, не даст жадному сборщику мусора «съесть» объект раньше времени. Если же по какой-то причине мы не можем или не хотим вызывать Dispose (к примеру WPF этот шаблон совсем не жалует), то придется вставлять GC.KeepAlive(wr) после вызова DoCalculations.

Читать:
Как в автокаде закрепить изображение

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

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

История 2

Жил-был проект, где для доступа к ресурсам использовался менеджер, который по строковому ключу доставал из заданной сборки различного вида ресурсы. С целью облегчения написания кода был написан следующего вида метод:

public string GetResource( string key)
<
Assembly assembly = Assembly .GetCallingAssembly();
return this .GetResource(assembly, key);
>

* This source code was highlighted with Source Code Highlighter .

После миграции на .Net 4 некоторые ресурсы внезапно перестали находиться. И дело тут опять же в оптимизации релизной версии. Дело в том, что в 4 версии дотнета компилятор может встраивать вызовы в код методов других сборок.

Чтобы «почувствовать разницу» предлагается следующий пример:

dll1:
public class Class1
<
public void Method1()
<
Console .WriteLine( new StackTrace());
>
>

dll2:
public class Class2
<
public void Method21()
<
this .Method22();
>

public void Method22()
<
( new Class1()).Method1();
>
>

dll3:
class Program
<
static void Main( string [] args)
<
( new Class3()).Method3();
>
>
class Class3
<
public void Method3()
<
( new Class2()).Method21();
>
>

* This source code was highlighted with Source Code Highlighter .

Если скомпилировать в дебажной конфигурации(или если запускать процесс из-под студии) то получим честный стек вызовов:
в ClassLibrary1.Class1.Method1()
в ClassLibrary2.Class2.Method22()
в ClassLibrary2.Class2.Method21()
в ConsoleApplication1.Class3.Method3()
в ConsoleApplication1.Program.Main(String[] args)

Если собрать под .Net версии до 3.5 включительно в релизе:
в ClassLibrary1.Class1.Method1()
в ClassLibrary2.Class2.Method21()
в ConsoleApplication1.Program.Main(String[] args)

А под .Net 4 в релизной конфигурации то и вовсе получим:
в ConsoleApplication1.Program.Main(String[] args)

Мораль здесь проста — не стоит привязывать логику к стеку вызовов, равно как и удивляться необычному стеку в исключениях в логе релизной версии. В частности, если вы пытаетесь найти причину исключения исключительно по его стеку вызовов, то стоит учитывать, что если стек заканчивается на методе Method1, то в коде оно(исключение) могло быть сгенерировано в одном из небольших методов, которые вызываются в теле Method1.

Так же на всякий случай стоит помнить, что можно запретить компилятору встраивать метод пометив его атрибутом [MethodImpl(MethodImplOptions.NoInlining)], эдакий аналог __declspec(noinline) в VC++.

Вместо заключения

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

kaktusenok

Начинающим программистам всегда интересно, чем отличается конфигурация Debug от Release.

Привожу свой ответ на этот вопрос.

Главное различие состоит в назначении: конфигурация Debug предназначена для компиляции на этапе разработки и отладки программы, а Release — для сборки программы и последующего её использования пользователями программы.

  • В конфигурации Release удаляется отладочная информация из исполняемого файла. Это приводит к уменьшению размера исполняемого файла (обычно в несколько раз).
  • Исключаются дополнительные проверки. Например, инициализированы переменные или нет:

Из приведёного рисунка видно, что в конфигурации Release никаких проверок не осуществляется, а в конфигурации Debug они есть. Так в STL, например, производятся дополнительные проверки итераторов перед операциями инкремента и декремента:

Единственный способ установить их — скопировать необходимые DLL с компьютера разработчика. Для исполняемого файла, скомпилированного в конфигурации Release, можно установить все необходимые библиотеки из распространяемого пакета Microsoft Visual C++, чтобы избежать такой ошибки:

Урок №6. Режимы конфигурации «Debug» и «Release»

Конфигурация сборки (англ. «build configuration») — это набор настроек проекта, которые определяют принцип его построения. Конфигурация сборки состоит из:

имени исполняемого файла;

имени директории исполняемого файла;

имён директорий, в которых IDE будет искать другой код и файлы библиотек;

информации об отладке и параметрах оптимизации вашего проекта.

Конфигурация «Debug» предназначена для отладки вашей программы. Эта конфигурация отключает все настройки по оптимизации, включает информацию об отладке, что делает ваши программы больше и медленнее, но упрощает проведение отладки. Режим «Debug» обычно используется в качестве конфигурации по умолчанию.

Конфигурация «Release» используется во время сборки программы для её дальнейшего выпуска. Программа оптимизируется по размеру и производительности и не содержит дополнительную информацию об отладке.

Например, исполняемый файл программы «Hello, World!» из предыдущего урока, созданный в конфигурации «Debug», у меня занимал 65 КБ, в то время как исполняемый файл, построенный в конфигурации «Release», занимал всего лишь 12 КБ.

Переключение между режимами «Debug» и «Release» в Visual Studio

Самый простой способ изменить конфигурацию проекта — выбрать соответствующую из выпадающего списка на панели быстрого доступа:

Переключение между режимами «Debug» и «Release» в Code::Blocks

В Code::Blocks на панели быстрого доступа есть также выпадающий список, где вы можете выбрать соответствующий режим конфигурации:

Заключение

Используйте конфигурацию «Debug» при разработке программ, а конфигурацию «Release» при их релизе.

Похожие статьи