[С] тупик дебаг и релиз сборок
Спасибо, капитан. Осталось рассказать ещё про стрип дебаг-символов в отдельный пакет, который клиенту, например, вообще отправлять не обязательно.
Спасибо, капитан.Многие просили материалы из курса, который вяло, но идёт. Мне показалась эта тема интересной, я ещё хочу запостить некоторые.
#ifdef DEBUG
# define BUG_ON(x) if (x) { падаем }
#elif
# define BUG_ON(x) void)sizeof(x
#endif
Кто-то когда-то придумал делать сборки debug и release, отличающиеся тем, что в первом случае код компилируется годный для отладки, а во втором — оптимизированный для продакшена.имхо:
1) это разделение было придумано (и до сих пор в основном используется) на винде
2) ключевое отличие (на винде про которое ты не упомянул (видимо потому что винда не актуальна) - использование этими сборками разных рантайм-библиотек.
P.S. на юниксах/линуксе всегда всё собираю с -g и -fno-omit-frame-pointer (аналог для SunCC: -xregs=no%frameptr)
в итоге придумали что-то типа этогоТак вычисление x как бы повлечёт падение производительности. Ну не упал ты, и что? Разницы никакой.
Так вычисление x как бы повлечёт падение производительности.а ты замерял это падение именно на своём софте? может, оно вообще не актуально?
ключевое отличие (на винде про которое ты не упомянул (видимо потому что винда не актуальна) - использование этими сборками разных рантайм-библиотекА уже под Линуксом это тоже стало нормой, даже пакеты идут отдельные дебажные.
[pollstart]
[polltitle=Что вы думаете о debug и release сборках?]
[polloption=Я использую их, это удобно и позволяет эффективно отлаживать]
[polloption=У нас так принято в команде, но я особо не пользуюсь данной фичей]
[polloption=У нас на проекте это не навязывается, я и не использую]
[polloption=Я не знаю что такое debug и release]
[polloption=Пунктик для гостей и троллей]
[pollstop]
Пунктик для гостей и троллейпридётся сюда записаться хотя мне бы подошёл вариант "использую debug-сборку когда нужен debug heap или его аналоги" (под аналогами на юниксах подразумеваю опции компилятора, включающие различные runtime-проверки, в основном по памяти - типа +check у HP aCC)
ты не понял. если кто-то патчит код и собирает его без дебага то он может сломать компиляцию кода с дебагом. а этот sizeof проверяет что выражение валидное но не вычисляет его.
Далее, неприятный вариант конечно тоже случается, но крайне редко. Хотя, конечно, такие случаи запоминаются намного лучше, чем в сотни или тысячи раз более частые баги, которые удаётся легко и приятно отловить в дебаг режиме — благодаря присутствующим там проверкам. Это называется selection bias.
Итак, идеальная ситуация это когда падения в релизе являются подмножеством падений в дебаге, причём последние триггерятся как можно большим количеством багов. Правильные компиляторы (например, Microsoft Visual C++) прилагают целенаправленные усилия для продвижения в этом направлении: так, неинициализированная память инициализируется специальными значениями (а вовсе не нулями обращение к неинициализированной переменной как правило приводит к немедленному падению (уж не знаю, как они этого добиваются вокруг аллоцированных блоков и между стекфреймами тоже оставляются проинициализированные магическими значениями зазоры (которые автоматически проверяются на предмет коррапшена и так далее. GCC, кажется, не настолько продвинут в этом смысле, возможно, в этом твоя проблема! =)
она поможет всего лишь всплыть отладчику, когда управление случайно передастся на данные и возможно значение указателя 0xCCCCCCCC показывает в область, которая сразу даст segfault при обращении. Просто "переменную" ты этим никак не спасешь.
это еще теоретически помогает всплать багам с неинициализированными данным: нуль чаще бывает допустимым значением, чем что-нибудь большое.
stack-protection в gcc тоже есть. фиксированный poison для неинициализированных данных не лучшая идея — может так получится что с ним всё работает. уж лучше случайно замазывать.
это еще теоретически помогает всплать багам с неинициализированными данным:Как правило, этому багу помогает всплыть повышение уровня предупреждений компилятора (а вот за его понижение уже надо бить). Меня иногда задалбывает, что гцц от меня требует даже в конструкторе класса проинициализировать все поля — даже те, которые являются объектами — но мне кажется, что это в целом совершенно правильно.
Правильные компиляторы (например, Microsoft Visual C++) прилагают целенаправленные усилия для продвижения в этом направлении:т.к. это негативно влияет на производительность, то производят эти действия только в дебаге
хотя включить можно и в релизе из кода
пример: http://msdn.microsoft.com/en-us/library/5at7yxcs%28v=vs.71%2...
1) пример неудачный
2) ненавижу #ifdef DEBUG
3) ненавижу функции с переменным числом аргументов
ненавижу функции с переменным числом аргументовпочему? а как же функции типа printf?
а как же функции типа printf?я ее ненавижу тоже
из-за отсутствия проверки типов и крашей
функция, которая не может что-то сделать должна вернуть код ошибки\исключение, а не падать
из-за отсутствия проверки типов и крашейприличные современные компиляторы таки проверяют типы аргументов printf на этапе компиляции
ах, да, в хвалёном (выше) Microsoft Visual C++ это только начиная с версии 1600 (VC10 только с включённым static analyzer, и только при 32-битной сборке (ни в 64-битном компиляторе, ни в кросс-компиляторе static analyzer не доступен).
из-за отсутствия проверки типов и крашейМожет тебе ещё выход за пределы массива нужен?
Ты не забывай, что этот системное программирование, а не кодирование на высоком уровне. Проверка типов — накладной расход, который, кстати, ничего не гарантирует! Ты же можешь подсунуть ту же const char* совершенно не валидную.
приличные современные компиляторы таки проверяют типы аргументов printf на этапе компиляцииgcc это давно делает, по-моему с 4 версии. Уже есть даже резервные слова для того, чтобы формат и параметры компилятор проверял в произвольных функциях.
Проверка типов — накладной расход, который, кстати, ничего не гарантирует!Всё ещё хуже. Например абсолютно безопасный memcpy можно реализовать только сисколом, иначе никак не убрать рэйсы с unmap в соседнем трэде.
Ты не забывай, что этот системное программирование, а не кодирование на высоком уровне.неверно: это мрачный язык С, а не системное программирование
С - язык общего назначения, а printf кривая функция ядра языка, которая еще и с локалями неявно работает, которые нафиг не нужны в системном программировании
Проверка типов — накладной расход, который, кстати, ничего не гарантирует!я не предлагал вводить рефлекшен и в рантайме проверять типы
с этим должен справляться компилятор
гарантировать отсутствие крашей вполне можно было на уровне дизайна
возможность проверки компилятором формата с типами аргументов - это костыль, который далеко не сразу прикрутили и который не решает проблему полностью: выводится ворнинг вместо ошибки компиляции + невозможно проверить формат, определяемый в рантайме
и людям приходится выпиливать велосипеды из гранита: http://www.fastformat.org/
гарантировать отсутствие крашей вполне можно было на уровне дизайнаникак ты это не гарантируешь, просто неверная const char* — и ты в сегфолте.
А что тебе не нравится в сегфолтах? Это нормальное состояние программы на момент написания. По сути это аппаратная проверка условий так срабатывает.
возможность проверки компилятором формата с типами аргументов - это костыль, который далеко не сразу прикрутили и который не решает проблему полностью: выводится ворнинг вместо ошибки компиляции + невозможно проверить формат, определяемый в рантаймеЭто не костыль, это ворнинг. А ты разве компилируешь программы без -Wall? Все ворнинги надо вычищать, иначе это плохо написанная программа.
Что такое «формат, определяемый в рантайме»?
Все ворнинги надо вычищать, иначе это плохо написанная программамы и так переключились с дебуг\релиза на функции с ..., а ты еще один вброс делаешь
Что такое «формат, определяемый в рантайме»?как-то так: http://ideone.com/rC95M
как-то так
Никогда так не пиши! Это же ахтунг...
Никогда так не пиши!ОК
Если нужно много языков поддерживать, то gettext используй, по крайней мере будешь сразу видеть в одной строчке и формать, и данные какие передаёшь. А при переводе — на совести переводчика будут строчки.
printf(_("Hello, %s! You have %u points!" name, points);
Не, я серьёзно.я тебе на данном примере объяснил что значит «формат, определяемый в рантайме»
надеюсь, мне удалось
если бы все считали, что любой пример с динамическим форматом является "ахтунгом", то данное использование могли бы запретить и на уровне стандарта и на уровне компилятора: не компилировать такой код
"За выполнение задания вы получите 13200 кредитов, а если успеете за 62140 кредитов, то получите дополнительно 12 дней".
(это, очевидно, "You will get %s, and %s more if you arrive in %s").
Может тебе ещё выход за пределы массива нужен?Есть правильный Си, в котором отсутствие выходов за пределы массива и невалидных указателей проверяется-таки статически, без накладных расходов в рантайме. Называется ATS:
Ты не забывай, что этот системное программирование, а не кодирование на высоком уровне. Проверка типов — накладной расход, который, кстати, ничего не гарантирует! Ты же можешь подсунуть ту же const char* совершенно не валидную.
http://thedeemon.livejournal.com/41035.html
Программистов сей, которые ещё не выучили, что существует выделенный
символ NDEBUG, надо убивать. Желательно --- как можно жесточе.
> она будет пищать ворнингами
Программистов сей, которые ещё не выучили, что программа должна
не только проходить lint (splint или иной анализатор но ещё и
собираться с флагами типа -Werror, надо убивать.
Желательно --- как можно жесточе.
> Холивар-тема для обсуждения: все программы собирать и
> эксплуатировать в среднем варианте, когда не теряются
> таблицы символов и не делаются чудовищно медленные проверки
> везде и всюду в виде ассёртов. Этот вариант использовать
> и при разработке, и на продакшене, имея всегда предсказуемо
> одинаковое поведение программ.
Я вот думаю: как же это так наши коллеги ваяют высоконагруженный
код на яве, да ещё и запихивают его во встраиваемую систему?
---
"Мы диалектику учили не по Гегелю.
Бряцанием боёв она врывалась в стих..."
Программистов сей, которые ещё не выучили, что существует выделенныйNDEBUG — исключительно для ассертов, и то должен выставляться программистом. Что касается кастомных отладочных вызовов, то тут уже воля твоя, что использовать. Студия вон, дефайнит __DEBUG.
символ NDEBUG, надо убивать. Желательно --- как можно жесточе.
В остальном, впрочем, не спорю.
Программистов сей, которые ещё не выучили, что программа должнаA narrowness of experience leads to the narrowness of imagination.
не только проходить lint (splint или иной анализатор но ещё и
собираться с флагами типа -Werror, надо убивать.
Желательно --- как можно жесточе.
Не программистом, а отдельным человеком, следящим за сборкой, от
программиста требуется только неизобретение самопальных ассертов
и тому подобной ерунды. (Или, если уж изобретает, пусть изобретает
более вменяемые ассерты, которые посылают стек в syslog, сохраняют
дамп памяти процесса и корректно всё перезапускают, не дожидаясь
сторожа.)
> Что касается кастомных отладочных вызовов, то тут уже воля твоя, что использовать.
Я вот думаю: что же вы такое пишете, что не является
лабораторной работой и можно просто так пересобрать для отладки?
Как только продукт уйдёт в поле, усилия на написание отладочного
кода с привлечением препроцессора будут бесполезны: никакой
заказчик не будет выполнять никаких телодвижений, чтобы
удовлетворить любопытство разработчика. Тот, который будет, это
очень редкое исключение. Так что все эти DEBUG и __DEBUG, если
они не управляют ассертами, должны быть переменными,
определяющими, что пишется в журнал, а это уже совсем другая
сказка.
---
"Vyroba umelych lidi, slecno, je tovarni tajemstvi."
Я вот думаю: что же вы такое пишете, что не являетсяТо есть, версии, что продукт может писаться не для заказчика, а для себя, ты не допускаешь? Или что производительность может быть критична настолько, что нельзя в методе, вызываемом >1М раз в секунду, лишний раз проверять какую-то переменную? Особенно, если она может лежать в конфиге, который может кем-то в рантайме меняться.
лабораторной работой и можно просто так пересобрать для отладки?
Как только продукт уйдёт в поле, усилия на написание отладочного
кода с привлечением препроцессора будут бесполезны: никакой
заказчик не будет выполнять никаких телодвижений, чтобы
удовлетворить любопытство разработчика. Тот, который будет, это
очень редкое исключение. Так что все эти DEBUG и __DEBUG, если
они не управляют ассертами, должны быть переменными,
определяющими, что пишется в журнал, а это уже совсем другая
сказка.
А работает-то она где? В лаборатории?
> Или что производительность может быть критична настолько, что
> нельзя в методе, вызываемом >1М раз в секунду, лишний раз
> проверять какую-то переменную?
Это теоретические измышления или у вас практика такая?
Как такой код ушёл в производство, если в нём время от времени
для отладки приходится вставлять учёт событий? Или вы "железо"
так проверяете?
> Особенно, если она может лежать в конфиге, который может
> кем-то в рантайме меняться.
Как это мешает? Или вы конфигурационный файл перечитываете раз в секунду?
---
...Я работаю антинаучным аферистом...
У тех, кто пишет код. Не в лаборатории.
> Как такой код ушёл в производство, если в нём время от времени
> для отладки приходится вставлять учёт событий? Или вы "железо"
> так проверяете?
Не учёт, а вывод дополнительной информации о них на консоль, например. Вы, очевидно, адепты практики дебага методом пристального разглядывания? Или настоящие джедаи дебажат только в гдб? Ну так поведение программы в гдб отличается от нормального. Я что-то плохо понимаю, какой способ отладки ты пропагандируешь.
> Как это мешает? Или вы конфигурационный файл перечитываете раз в секунду?
Это мешает как минимум тем, что ты не имеешь права закэшировать параметры конфига локально, например, прямо в функции, а будешь вынужден обращаться каждый раз к классу конфига, просить у него выдать соответствующий параметр. Все эти вызовы тоже будут давать накладные расходы.
Оставить комментарий
Werdna
Пока медленно, но верно движется курс, буду выносить на обсуждение интересные вопросы. Интересные они тем, что нет однозначного ответа на них.Кто-то когда-то придумал делать сборки debug и release, отличающиеся тем, что в первом случае код компилируется годный для отладки, а во втором — оптимизированный для продакшена. Я выскажу всем очевидное требование, но далеко не всегда выполняемое: debug и release сборки обязаны иметь одинаковое поведение на одинаковых исходных данных. В первую очередь это касается, конечно же, сегволтов и прочих экстремальных ситуаций.
Теперь возьмём классические примеры, когда они могут нарушаться: assert'ы и debug-вывод в логи. К сожалению, моя практика показывает, что уследить за ассёртами ещё как-то можно, то за всем остальным — нет.
Рассмотрим такой пример:
Что тут совсем плохо? Прежде всего то, что код может упать как раз на неверном формате и данных. В релизе проскочит и пойдёт дальше:
Если в продакшене всё будет падать в функции с ошибкой, то в дебаге — ДО этого, причем не сразу догадаешься где, если кода много. Конечно же, это лечится, и определение функции debug можно довесить __attribute__ (__printf__, 1, 2... она будет пищать ворнингами, но это не единственная ситуация.
Другой пример, придумывать специально лень, но очень реалистичен и часто бывает на практике. После отработки дебаг-функции чудесным образом чистится стек, который в релизной сборке приводит к падению. Да, где-то дальше идёт ошибка выхода за стек, но получив сегфолт в релизе вы его не воспроизведёте в дебаге.
Холивар-тема для обсуждения: все программы собирать и эксплуатировать в среднем варианте, когда не теряются таблицы символов и не делаются чудовищно медленные проверки везде и всюду в виде ассёртов. Этот вариант использовать и при разработке, и на продакшене, имея всегда предсказуемо одинаковое поведение программ.