Pull to refresh

Comments 25

В теории всё это звучит хорошо, однако как показал опыт самый простой способ контролировать память как ни странно старый добрый malloc/free.
В режиме отладки добавляется глобальный дефайн malloc -> malloc_log и аналогичные для остальных и, в зависимости от режима отладки, либо всё пишется в лог, либо просто считается баланс выделения-освобождения. Если баланс на выходе не сходится — переключаем в полный лог, в который пишем размер, адрес, файл и строку. Дальше тупой скрипт на perl выдаёт статистику — что не было освобождено или не дай бог освобождено без выделения. Утечки ловятся на раз, т.к. в режиме баланса на отладке он работает всегда. Правда с хитрыми утечками, которые возникают только при редких стечениях обстоятельств работает плохо. Логи, правды, бывают большими. Порой по пол-гига набегало.
Как сделать аналогичное с new придумать не удалось.
> Как сделать аналогичное с new придумать не удалось.
Хороший детектор для Visual Studio, в т.ч. для new. Можно подсмотреть как сделано.
http://www.codeproject.com/Articles/9815/Visual-Leak-Detector-Enhanced-Memory-Leak-Detectio
Спасибо, весьма любопытная штука. Как и то, как именно она это делает.Правда один минус — не ловит «излишки», т.е. попытки освободить то, что не было выделено (случается про повреждении указателей). Очень мерзкая вещь, поскольку обычно вызывает падение программы без каких-либо явных симптомом.
Еще не очень понял — оно работает только с VS или можно куда угодно прикрутить?
Не знаю, пробовал только в VS. В остальных местах же есть Valgrind?
Valgrind судя по вики есть только в Linux. Под Windows список компиляторов VS не ограничивается. Сам, например, сейчас пишу под mingw.
однако как показал опыт самый простой способ контролировать память как ни странно старый добрый malloc/free.

Вы описываете не способ контролировать, а способ бороться с последствиями такого "контроля". Не говоря уже о том, что вся статья посвящена как раз тому что утечки и расстрел памяти — разные вещи и раст обещает именно отсутствие второго.

В моей практике (С++) весьма неплохо работают умные указатели в связке с валгриндом — как раз чтобы уменьшить человеческий фактор. Подозреваю, что достаточного опыта для полноценного сравнения с Rust у вас нет. Впрочем, я и сам не сильно дальше "хелоу ворлдов" ушёл, так что спорить не буду. Хотя растовые гарантии мне кажутся весьма полезными.

Увы, да. Хотя Rust очень заинтересовал своим подходом.
А что такое «валгринд»?
А что такое «валгринд»?

Процитирую википедию: "Valgrind — инструментальное программное обеспечение, предназначенное для отладки использования памяти, обнаружения утечек памяти, а также профилирования." Правда под Windows им пользоваться не получится.

>> Как сделать аналогичное с new придумать не удалось.
void* operator new(std::size_t n)
void* operator new[]( std::size_t count )
void operator delete(void* ptr)
void operator delete[]( void* ptr )
Ну и placement new, new(nothrow) если они вам нужны.
Чем городить велосипед можно использовать готовое решение — в clang есть address sanitizer и leak sanitizer. Включаются тривиально — просто опцией при компиляции, работают быстро (относительно валгринда), сразу выдают читаемые сообщения об ошибках.

Для более корректной работы нужно так же компилировать все сторонние библиотеки, кроме системных вроде libc. Можно и без этого, но он начнёт пропускать ошибки. Так что работают‐то sanitizer’ы быстро, но всё имеет свою цену.

Я когда использую его подключаю не инструментированные clang'ом libstdc++ и boost, остальные используемые библиотеки относительно небольшие и они и так компилируются clang в рамках одного проекта и cmake файла, поэтому для них добавить ключ компиляции не проблема. При этом sanitizer помогает почти мгновенно найти как обращение к «чужой» памяти (это меньшая проблема т.к. обычно отлавливается и просто при запуске в дебаггере — однако sanitizer даёт дополнительную полезную инфу) так и утечки памяти.
Занятно. А можно где-нибудь про него почитать нормальны доки? Что-то находятся только какие-то обрезки.
Ну и, собственно, та же проблема (как я понял по обрывкам). Он попытки освобождения повреждённого указателя не видит.
А какие собственно доки нужны, кроме того что есть на http://clang.llvm.org/docs/AddressSanitizer.html? Если просто добавить указанный ключ и ASAN_SYMBOLIZER_PATH=/usr/local/bin/llvm-symbolizer при запуске, то в случае ошибки обращения к памяти или утечки он выдаст подробную информацию. Так что ничего настраивать не надо особо — просто в системе сборки указать дополнительный ключ компиляции и всё (конечно, если у вас clang используется; и обнаружение утечек работает только на linux).
Вообще я с mingw работаю, но в gcc похоже эта фишка тоже есть. Так что есть надежда, что и в mingw. Хотя если только на linux…
В gcc они же есть с 4.8: https://gcc.gnu.org/gcc-4.8/changes.html
Спасибо за информацию, не знал — всегда clang использую. Не нашёл по вашей ссылке детектор утечек правда — судя по гуглу, его тоже добавили, но совсем недавно.
Leak sanitizer встроен в Address sanitizer, причём судя по этому issue он там появился раньше чем сам Address Sanitizer стал встроен в gcc. В любом случае, он есть и работает, с gcc 5 точно. Например, недавно благодаря ему узнал про mpfr_free_cache.
Все ссылки оказались съедены…

Leak sanitizer всроен в Address: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html#index-fsanitize_003dleak-950
Issue: https://github.com/google/sanitizers/issues/118
mpfr_free_cache: http://www.mpfr.org/mpfr-current/mpfr.html#index-mpfr_005ffree_005fcache
Это всё, конечно, очень хорошо, но до тех пор, пока счёт аллокаций не идёт на сотни миллионов, и сама программа не начинает занимать под сотню гигабайт в памяти.

Ну и да, valgrind, asan, google perftools.
Ну да, это уже другой уровень. Хотя метод вполне модифицируется и под такой вариант. Только вместо лога придётся отслеживать всё в рилтайме. С памятью тут проблем явно не будет под это. По сути это уже будет примерно то же самое, что и другие решения. Примерно.
Это же вроде как всем прописывать придётся, операторы у каждого объекта свои. Если у тебя их штук так дофига — это уже проблема, и отключить их в релизе так просто не получится. В теории можно переопределить у корневого (при наследовании) но смысла нет, т.к. не будет понятно откуда он вызван.
Хотя возможно тут я не прав, голый C мне ближе чем ++
(пардон, это было к комментарию про «void* operator new(std::size_t n)»)
Нет, их можно переопределить глобально.
А можно просто переопределить через LD_PRELOAD malloc/free и glibc'шными же средствами получать стектрейс каждой аллокации или освобождения. Заодно решается проблема с инструментацией сторонних библиотек, не собираемых в рамках проекта.

Собственно, google perftools примерно так и поступает.

Конечно, это всё под Linux. Может, под Windows есть аналогичные методы, не знаю.
Под Windows для поиска утечек памяти есть утилиты gflags и umdh из Debugging Tools for Windows. Сначала делаете:

gflags -i program.exe +ust

Подставив вместо program.exe имя своей программы, естественно. Это включает для program.exe логирование выделения/удаления блоков в куче со стеком. Это нужно делать ТОЛЬКО перед поиском утечек, т.к. всё будет работать существенно медленней. Потом запускаете программу и выполняете:

umdh -pn:program.exe -f:dump1.txt

Делается первый слепок памяти (все выделенные блоки со стеками). Дали поработать, поутекать памяти, потом делаете:

umdh -pn:program.exe -f:dump2.txt
umdh dump1.txt dump2.txt > diff.txt

В файл diff.txt выводятся все блоки памяти (количество, объём, стек), сгруппированные по стеку, которые либо был выделены и удалены, либо выделены до первого вызова umdh и удалены до второго. В конце нужно не забыть сделать:

gflags -i program.exe -ust
Only those users with full accounts are able to leave comments. Log in, please.