Як знайти витік пам'яті в коді / проекті C ++?


180

Я програміст на C ++ на платформі Windows. Я використовую Visual Studio 2008.

Зазвичай у коді я потрапляю з витоком пам'яті.

Зазвичай я знаходжу витік пам'яті, перевіряючи код, але це громіздко і не завжди є хорошим підходом.

Оскільки я не можу дозволити собі інструмент виявлення витоку пам'яті з оплачуваною пам'яттю, я хотів, щоб ви, хлопці, запропонували найкращі способи уникнути витоку пам'яті.

  1. Я хочу знати, як програміст може виявити витоки пам'яті.
  2. Чи є якісь стандарти або процедури, яких слід дотримуватися, щоб упевнитись, що в програмі немає витоку пам'яті?

29
"Зазвичай у коді я потрапляю з витоком пам'яті." Якщо ви використовуєте автоматичні змінні, контейнери та смарт-покажчики (і дотримуєтесь кращих практик використання розумних покажчиків), витоки пам'яті повинні бути вкрай рідкісними. Пам’ятайте, майже у всіх випадках слід використовувати автоматичне управління ресурсами .
Джеймс Мак-Нілліс

Duplicates питання , що охоплюються кілька питань, як stackoverflow.com/questions/1502799 / ... і stackoverflow.com/questions/2820223 / ...
HostileFork говорить не довіряю SE

1
@Hostile Fork: "як можна уникнути, як правило , у коді з витоками пам'яті" ці відповіді не охоплені.
Док Браун

2
@Doc Браун: Чи не відчуваю, дивлячись що занадто, але це все покрито в інших місцях, таких як stackoverflow.com/questions/45627 / ...
HostileFork говорить не довіряю SE

1
DIY детектор витоку: Ви можете помістити підозрюваний код у нескінченний цикл, а потім відкрити диспетчер завдань, як правило, навіть невеликий витік заповнить пам'ять за лічені хвилини (це залежить від складності вашого коду та вашого процесора). Якщо цього не відбувається, цей фрагмент коду, ймовірно, не протікає.
Hello World

Відповіді:


270

Інструкції

Те, що вам потрібно

  • Володіння мовою C ++
  • C ++ компілятор
  • Налагоджувач та інші дослідницькі програмні засоби

1

Розуміти основи оператора. Оператор C ++ newвиділяє кучу пам'яті. deleteОператорських звільняє пам'ять купи. Для кожного newслід використовувати deleteтак, щоб ви звільнили ту ж пам’ять, яку ви виділили:

char* str = new char [30]; // Allocate 30 bytes to house a string.

delete [] str; // Clear those 30 bytes and make str point nowhere.

2

Перерозподіліть пам'ять лише у випадку, якщо ви її видалили. У наведеному нижче коді strотримує нову адресу з другим виділенням. Перша адреса безповоротно втрачається, як і 30 байтів, на які вона вказувала. Тепер їх неможливо звільнити, і у вас є витік пам'яті:

char* str = new char [30]; // Give str a memory address.

// delete [] str; // Remove the first comment marking in this line to correct.

str = new char [60]; /* Give str another memory address with
                                                    the first one gone forever.*/

delete [] str; // This deletes the 60 bytes, not just the first 30.

3

Дивіться ці призначення вказівника. Кожна динамічна змінна (виділена пам'ять на купі) повинна бути пов’язана з покажчиком. Коли динамічна змінна відмежовується від своїх покажчиків, її неможливо стерти. Знову ж таки, це призводить до витоку пам'яті:

char* str1 = new char [30];

char* str2 = new char [40];

strcpy(str1, "Memory leak");

str2 = str1; // Bad! Now the 40 bytes are impossible to free.

delete [] str2; // This deletes the 30 bytes.

delete [] str1; // Possible access violation. What a disaster!

4

Будьте обережні з місцевими вказівниками. Вказівник, який ви заявляєте у функції, виділяється на стек, але динамічна змінна, на яку він вказує, розподіляється на купі. Якщо ви не видалите його, воно збережеться після виходу програми з функції:

void Leak(int x){

char* p = new char [x];

// delete [] p; // Remove the first comment marking to correct.

}

5

Зверніть увагу на квадратні дужки після "видалити". Використовуйте deleteсамостійно, щоб звільнити один об'єкт. Використовуйте delete []квадратні дужки, щоб звільнити масив. Не робіть чогось подібного:

char* one = new char;

delete [] one; // Wrong

char* many = new char [30];

delete many; // Wrong!

6

Якщо витік все ж дозволений - я зазвичай шукаю його з делекером (перевірте це тут: http://deleaker.com ).


3
Вибачте за запитання-коментар, але як щодо параметрів функції без покажчиків? someFunction("some parameter")я повинен видалити "some parameter"в someFunction, після виклику функції, або вони автоматично видаляються?
19greg96

1
дякую за посилання на Deleaker, це дійсно зручний інструмент із акуратною інтеграцією у візуальну студію. Я міг би заощадити багато часу, використовуючи його. вказав мені на рядки, де я виділив пам’ять і не звільнив її. Чудово. І це дешево, порівняно з іншими знайденими нами витоками пам'яті.
this.myself

@ Джон Сміт Плз поясніть, що є правильним способом розгляду справ, подібних до випадку 3; str2 = str1; // Погано! Зараз 40 байт неможливо звільнити. як видалити str 1 тоді ??
Ніхар

1
Що робити, якщо ми використовуємо тип значення, як char *, int, float, ... і структура, як Vector, CString, і взагалі не використовуємо жодного "нового" оператора, це не спричинить витоку пам'яті, чи правильно?
123iamking

Я просто хочу сказати, що не торкнувся c ++ протягом майже 14 років ... але я з гордістю можу сказати, що зрозумів і пам’ятаю, як все це зробити завдяки книзі c ++, яку я досі маю та читаю, коли я ' м нудно c #. Ця книга - Ефективний C ++ Скотта Мітчелла. Боже, я любив цю книгу. Дякую Скотту!
JonH

33

Ви можете використовувати деякі методи у своєму коді для виявлення витоку пам'яті. Найпоширеніший і найпростіший спосіб виявити - визначити скажіть макрос, DEBUG_NEW і використовувати його, а також заздалегідь задані макроси, як __FILE__і __LINE__знайти місце витоку пам'яті у вашому коді. Ці заздалегідь задані макроси повідомляють вам про витік пам'яті файлів та рядків.

DEBUG_NEW - це лише MACRO, який зазвичай визначається як:

#define DEBUG_NEW new(__FILE__, __LINE__)
#define new DEBUG_NEW

Так що де б ви не використовувались new, він також може відслідковувати номер файлу та номер рядка, який може бути використаний для пошуку витоку пам'яті у вашій програмі.

І __FILE__, __LINE__це заздалегідь визначені макроси, які оцінюють відповідно до імені файлу та номера рядка, де ви їх використовуєте!

Прочитайте наступну статтю, яка дуже красиво пояснює техніку використання DEBUG_NEW з іншими цікавими макросами:

Детектор витоку пам’яті між платформами


З Вікіпедії ,

Debug_new посилається на техніку в C ++ для перевантаження та / або перевизначення оператора new та видалення оператора з метою перехоплення розподілу пам'яті та викликів делокації, і таким чином налагодження програми для використання пам'яті. Він часто включає визначення макросу з іменем DEBUG_NEW і робить нове стає чимось на зразок нового (_ FILE _, _ LINE _) для запису інформації про файл / рядок при розподілі.Microsoft Visual C ++ використовує цю методику у класах Microsoft Foundation. Існує декілька способів розширити цей метод, щоб уникнути використання макроредагування, в той час як все ще в змозі відображати інформацію про файл / рядок на деяких платформах. Існує багато властивих цим методам обмежень. Він застосовується лише до C ++ і не може зафіксувати витоку пам'яті за допомогою функцій C, як malloc. Однак, це може бути дуже просто у використанні, а також дуже швидким у порівнянні з деякими більш повними рішеннями налагодження пам'яті.


4
це #defineзіпсує перевантаження operator newта створить помилки компілятора. Навіть якщо вам це вдасться подолати, то все-таки перевантажені функції не будуть вирішені. Хоча методика хороша, іноді їй потрібно багато змін коду.
iammilind

1
@iammilind: Звичайно, ця методика не є вирішенням усіх проблем, і, безумовно, не застосовується у всіх ситуаціях.
Наваз

@Chris_vr: auto_ptrне працюватиме зі стандартними контейнерами , такими як std::vector, і std::listт.д. Див це: stackoverflow.com/questions/111478 / ...
Nawaz

Гаразд круто. Описано ФАЙЛ і рядок. Що таке operator newі які ці версії ви використовуєте?

14

Існує кілька відомих методик програмування, які допоможуть вам звести до мінімуму ризик отримання витоку пам'яті з перших рук:

  • якщо вам потрібно зробити власний динамічний розподіл пам'яті, пишіть newі deleteзавжди попарно, і переконайтеся, що код розподілу / угода розташування називається попарно
  • уникайте динамічного розподілу пам'яті, якщо можете. Наприклад, використовувати vector<T> tкуди завгодно замість цьогоT* t = new T[size]
  • використовуйте "розумні покажчики", як збільшення розумних покажчиків ( http://www.boost.org/doc/libs/1_46_1/libs/smart_ptr/smart_ptr.htm )
  • мій особистий фаворит: переконайтеся, що ви зрозуміли поняття власності на вказівник, і переконайтесь, що скрізь, де ви використовуєте вказівники, ви знаєте, яка сутність коду є власником
  • дізнайтеся, які конструктори / оператори присвоєння автоматично створюються компілятором C ++, і що це означає, якщо у вас є клас, який володіє вказівником (або що це означає, якщо у вас є клас, який містить вказівник на об'єкт, якому він не володіє).

Я використовую auto_pointer об’єкта, це означає, що він видалить усі вказівники інших класів об'єкта всередині нього.
Chris_vr

@Chris_vr: якщо у вас є конкретне запитання щодо auto_pointer, я б запропонував вам поставити нове запитання, зокрема приклад.
Док Браун

Багато публікацій говорять про те, що вектор <> не гарантує звільнення пам'яті після очищення. Я особисто випробовував swap і т. Д. І прийшов до висновку, що вектор <> протікає особливо при динамічному використанні. Я не розумію, як вектор <> можна порадити щодо динамічного розподілу "зробі сам", використовуючи "new" та очищення правильно. У своїх вбудованих програмах я уникаю використання вектора <> для динамічних речей через усі витоки. Там я використовую new or std :: list
bart s

Я набираю другу команду через кількість символів. На жаль, у моєму вбудованому файлі c ++ у мене є старий c ++ (98?), Який не має shrink_to_fit у векторі ... Однак вбудована програма на 100% впевнена, щоб повністю вийти з ладу, коли вичерпано пам'ять, використовуючи вектор <> динамічно
bart s


8
  1. Завантажте інструменти налагодження для Windows .
  2. Використовуйте gflagsутиліту, щоб увімкнути сліди стека в режимі користувача.
  3. Використовуйте UMDH для отримання декількох знімків пам'яті програми. Зробіть знімок до виділення пам’яті та зробіть другий знімок після моменту, коли ви вважаєте, що у вашій програмі просочилася пам’ять. Ви можете додати в програму паузи чи підказки, щоб дати можливість запустити UMDHта зробити знімки.
  4. Біжи UMDH знову, на цей раз у своєму режимі, який робить різницю між двома знімками. Потім він створить звіт, що містить стеки викликів підозрюваних витоків пам'яті.
  5. Відновіть свої попередні gflagsналаштування, коли закінчите.

UMDHдасть вам більше інформації, ніж купа налагодження CRT, оскільки вона переглядає розподіл пам'яті протягом усього процесу; він навіть може сказати вам, якщо сторонні компоненти просочуються.


1
Я віддаю перевагу Deleaker і Valgrind замість стандартного
профілера

8

Запуск "Valgrind" може:

1) Допомога Ідентифікувати витоки пам’яті - покажіть, скільки у вас є витоків пам’яті, і вкажіть на рядки в коді, де виділена пам’ять.

2) Вкажіть на помилкові спроби звільнити пам'ять (наприклад, неправильний виклик delete)

Інструкція щодо використання "Valgrind"

1) Сюди потрапляйте .

2) Складіть свій код із -gпрапором

3) У вашій оболонці запустіть:

valgrind --leak-check=yes myprog arg1 arg2

Де «MyProg» ваша програма компілюється і arg1, arg2аргументи вашої програми.

4) Результатом є список дзвінків до malloc/ у newяких не було наступних дзвінків на безкоштовне видалення.

Наприклад:

==4230==    at 0x1B977DD0: malloc (vg_replace_malloc.c:136)

==4230==    by 0x804990F: main (example.c:6)

Повідомляє вам, у якому рядку mallocдзвонили (що не було звільнено).

Як вказували інші, переконайтеся, що для кожного new/ mallocдзвінка ви маєте наступний delete/ freeдзвінок.


6

Якщо ви використовуєте gcc, доступний gprof.

Мені хотілося знати, як програміст знаходить витік пам'яті

Деякі використовують інструменти, деякі - те, що ви робите, - це також можна зробити за допомогою експертного огляду

Чи є стандарт або процедура, яких слід дотримуватися, щоб упевнитись, що в програмі немає витоку пам'яті

Для мене: коли я створюю об'єкти, що динамічно розподіляються, я завжди кладу код звільнення після цього, а потім заповнюю код між. Це буде добре, якщо ви впевнені, що в коді не буде винятків. В іншому випадку я використовую спробу (нарешті, не використовую C ++ часто).


деякий час ми не можемо видалити виділений у конструкторі. Що робити з цього приводу.
Chris_vr

5
  1. У візуальній студії є вбудований детектор витоку пам'яті під назвою C Runtime Library. Коли ваша програма завершиться після повернення основної функції, CRT перевіряє налагодження вашої програми. якщо в купі налагодження все ще виділяються блоки, то у вас є витік пам'яті.

  2. Цей форум обговорює декілька способів уникнути витоку пам'яті в C / C ++ ..


5

Шукайте у своєму коді на наявність подій newта переконайтесь, що всі вони виникають у конструкторі з відповідним видаленням у деструкторі. Переконайтеся, що це єдина можлива операція кидання в цьому конструкторі. Простий спосіб зробити це - загортати всі вказівники std::auto_ptrабо boost::scoped_ptr(залежно від того, чи потрібна вам семантика переміщення). Для всього майбутнього коду просто переконайтеся, що кожен ресурс належить об'єкту, який очищає ресурс у його деструкторі. Якщо вам потрібна семантика переміщення, тоді ви можете перейти до компілятора, який підтримує r-значення посилань (я вважаю, VS2010) та створити конструктори переміщення. Якщо ви не хочете цього робити, то можете скористатися різноманітними хитромудрими методами, пов'язаними з сумлінним використанням swap, або спробуйте бібліотеку Boost.Move.


видалити виділену пам’ять у конструкторі не завжди можливо. Як вирішити цю ситуацію
Chris_vr

@Chris_vr Що ти маєш на увазі? Якщо всі ваші члени вказівника scope_ptrs, і кожен ініціалізується індивідуально, то всі ті, хто був успішно побудований, видалять свої вказівники, а інші поки що не будуть утримувати покажчики на виділену пам'ять. Я прикладу приклад через кілька годин, коли повернуся з роботи додому.
Манкарсе

@Chris_vr: якщо у вас є конкретний приклад, опублікуйте це як нове запитання, щоб ми могли там його обговорити.
Док Браун

5

Ви можете використовувати інструмент Valgrind для виявлення витоків пам'яті.

Крім того, щоб знайти витік у певній функції, використовуйте вихід (0) в кінці функції, а потім запустіть її з Valgrind

`$` valgrind ./your_CPP_program 

5

Огляд перевірок автоматичної витоку пам'яті

У цій відповіді я порівнюю декілька різних перевірок витоку пам'яті в простому для розуміння прикладі витоку пам'яті.

Перш ніж нічого, перегляньте цю величезну таблицю у вікі ASan, яка порівнює всі відомі людині інструменти: https://github.com/google/sanitizers/wiki/AddressSanitizerComppareOfMemoryTools/d06210f759fec97066888e5f27c7e722832b0924

Проаналізований приклад:

main.c

#include <stdlib.h>

void * my_malloc(size_t n) {
    return malloc(n);
}

void leaky(size_t n, int do_leak) {
    void *p = my_malloc(n);
    if (!do_leak) {
        free(p);
    }
}

int main(void) {
    leaky(0x10, 0);
    leaky(0x10, 1);
    leaky(0x100, 0);
    leaky(0x100, 1);
    leaky(0x1000, 0);
    leaky(0x1000, 1);
}

GitHub вище за течією .

Ми спробуємо побачити, наскільки чітко різні інструменти вказують нам на проникливі дзвінки.

tcmalloc від gperftools від Google

https://github.com/gperftools/gperftools

Використання в Ubuntu 19.04:

sudo apt-get install google-perftools
gcc -ggdb3 -o main.out main.c -ltcmalloc
PPROF_PATH=/usr/bin/google-pprof \
  HEAPCHECK=normal \
  HEAPPROFILE=ble \
  ./main.out \
;
google-pprof main.out ble.0001.heap --text

Вихід запуску програми містить аналіз витоку пам'яті:

WARNING: Perftools heap leak checker is active -- Performance may suffer
Starting tracking the heap
Dumping heap profile to ble.0001.heap (Exiting, 4 kB in use)
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 272 bytes in 2 objects
The 2 largest leaks:
Using local file ./main.out.
Leak of 256 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581d3 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start
Leak of 16 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581b5 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start


If the preceding stack traces are not enough to find the leaks, try running THIS shell command:

pprof ./main.out "/tmp/main.out.24744._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv

If you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find leaks more re
Exiting with error code (instead of crashing) because of whole-program memory leaks

а вихід google-pprofмістить аналіз використання кучі:

Using local file main.out.
Using local file ble.0001.heap.
Total: 0.0 MB
     0.0 100.0% 100.0%      0.0 100.0% my_malloc
     0.0   0.0% 100.0%      0.0 100.0% __libc_start_main
     0.0   0.0% 100.0%      0.0 100.0% _start
     0.0   0.0% 100.0%      0.0 100.0% leaky
     0.0   0.0% 100.0%      0.0 100.0% main

Вихід вказує на два з трьох витоків:

Leak of 256 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581d3 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start
Leak of 16 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581b5 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start

Я не впевнений, чому третій не з’явився

У будь-якому випадку, коли зазвичай, коли щось протікає, це трапляється багато разів, і коли я використовував це на реальному проекті, я просто в кінцевому підсумку дуже легко вказав на функцію протікання.

Як вже було сказано у висновку, це призводить до значного уповільнення виконання.

Подальша документація за адресою:

Дивіться також: Як користуватися TCMalloc?

Тестовано в Ubuntu 19.04, google-perftools 2.5-2.

Адреса Sanitizer (ASan) також від Google

https://github.com/google/sanitizers

Раніше згадувалося в: Як знайти витік пам'яті в коді / проекті C ++? TODO vs tcmalloc.

Це вже інтегровано в GCC, тому ви можете просто зробити:

gcc -fsanitize=address -ggdb3 -o main.out main.c
./main.out 

та результати виконання:

=================================================================
==27223==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 4096 byte(s) in 1 object(s) allocated from:
    #0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
    #1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
    #2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
    #3 0x55bf86c5f210 in main /home/ciro/test/main.c:20
    #4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)

Direct leak of 256 byte(s) in 1 object(s) allocated from:
    #0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
    #1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
    #2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
    #3 0x55bf86c5f1f2 in main /home/ciro/test/main.c:18
    #4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)

Direct leak of 16 byte(s) in 1 object(s) allocated from:
    #0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
    #1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
    #2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
    #3 0x55bf86c5f1d4 in main /home/ciro/test/main.c:16
    #4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)

SUMMARY: AddressSanitizer: 4368 byte(s) leaked in 3 allocation(s).

що чітко визначає всі витоки. Приємно!

ASan також може робити інші цікаві перевірки, такі як написання поза межами: " Стек виявлено

Випробувано в Ubuntu 19.04, GCC 8.3.0.

Valgrind

http://www.valgrind.org/

Раніше згадувалося на сайті: https://stackoverflow.com/a/37661630/895245

Використання:

sudo apt-get install valgrind
gcc -ggdb3 -o main.out main.c
valgrind --leak-check=yes ./main.out

Вихід:

==32178== Memcheck, a memory error detector
==32178== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==32178== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==32178== Command: ./main.out
==32178== 
==32178== 
==32178== HEAP SUMMARY:
==32178==     in use at exit: 4,368 bytes in 3 blocks
==32178==   total heap usage: 6 allocs, 3 frees, 8,736 bytes allocated
==32178== 
==32178== 16 bytes in 1 blocks are definitely lost in loss record 1 of 3
==32178==    at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178==    by 0x10915C: my_malloc (main.c:4)
==32178==    by 0x109179: leaky (main.c:8)
==32178==    by 0x1091B4: main (main.c:16)
==32178== 
==32178== 256 bytes in 1 blocks are definitely lost in loss record 2 of 3
==32178==    at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178==    by 0x10915C: my_malloc (main.c:4)
==32178==    by 0x109179: leaky (main.c:8)
==32178==    by 0x1091D2: main (main.c:18)
==32178== 
==32178== 4,096 bytes in 1 blocks are definitely lost in loss record 3 of 3
==32178==    at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178==    by 0x10915C: my_malloc (main.c:4)
==32178==    by 0x109179: leaky (main.c:8)
==32178==    by 0x1091F0: main (main.c:20)
==32178== 
==32178== LEAK SUMMARY:
==32178==    definitely lost: 4,368 bytes in 3 blocks
==32178==    indirectly lost: 0 bytes in 0 blocks
==32178==      possibly lost: 0 bytes in 0 blocks
==32178==    still reachable: 0 bytes in 0 blocks
==32178==         suppressed: 0 bytes in 0 blocks
==32178== 
==32178== For counts of detected and suppressed errors, rerun with: -v
==32178== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)

Тож ще раз було виявлено всі витоки.

Дивіться також: Як використовувати valgrind для пошуку витоків пам'яті?

Тестовано в Ubuntu 19.04, valgrind 3.14.0.


4

Візуальний детектор витоку (VLD) - це вільна, надійна система відкриття вихідної пам'яті з відкритим кодом для Visual C ++.

Коли ви запускаєте свою програму під налагоджувачем Visual Studio, в кінці сеансу налагодження Visual Leak Detector видасть звіт про витоки пам'яті. Звіт про витоки містить повний стек викликів, що показує, як розподілялися блоки витоку пам'яті. Двічі клацніть по рядку в стеку викликів, щоб перейти до цього файлу та рядка у вікні редактора.

Якщо у вас є лише звалища, ви можете скористатися командою Windbg !heap -l, вона виявить блоки витоку, що протікають. Краще відкрийте опцію gflags: "Створити базу даних слідів стека в режимі користувача", тоді ви побачите стек виклику розподілу пам'яті.


4

MTuner - це безкоштовне багатоплатформене профілювання пам'яті, інструмент виявлення та аналізу витоків, що підтримує компілятори MSVC, GCC та Clang. Особливості включають:

  • на основі часової шкали історія використання пам'яті та блоки живої пам'яті
  • потужна фільтрація операцій із пам'яттю на основі купи, тегу пам'яті, часового діапазону тощо
  • SDK для ручного приладобудування з повним вихідним кодом
  • безперервна підтримка інтеграції через використання командного рядка
  • виклик стека дерева та навігації по карті дерева
  • набагато більше.

Користувачі можуть профілювати будь-які платформи націлювання на програмне забезпечення за допомогою компіляторів GCC або Clang. MTuner має вбудовану підтримку платформ Windows, PlayStation 4 та PlayStation 3.


Це має бути прийнятою відповіддю. Це чудовий інструмент і може обробляти обсяги асигнувань / угод, які інші не можуть.
Серж Рогач

3

У Windows можна використовувати набір відладок CRT .

Чи є стандарт або процедура, яких слід дотримуватися, щоб упевнитись, що в програмі немає витоку пам'яті.

Так, не використовуйте ручне управління пам'яттю (якщо ви коли-небудь дзвоните deleteабо delete[]вручну, то ви робите це неправильно). Використовуйте RAII та смарт-покажчики, обмежте розподіл купи до абсолютного мінімуму (більшість часу буде достатньо автоматичних змінних).


3

Відповідаючи на другу частину вашого запитання,

Чи є стандарт або процедура, яких слід дотримуватися, щоб упевнитись, що в програмі немає витоку пам'яті.

Так, є. І це одна з ключових відмінностей між C і C ++.

У C ++ вам ніколи не слід телефонувати newабо deleteвводити код користувача. RAII - дуже поширена методика, яка в значній мірі вирішує проблему управління ресурсами. Кожен ресурс у вашій програмі (ресурс - це все, що потрібно придбати, а потім пізніше випустити: ручки файлів, мережеві розетки, підключення до бази даних, а також просте виділення пам'яті, а в деяких випадках пари викликів API (BeginX ( ) / EndX (), LockY (), UnlockY ()), слід загорнути в клас, де:

  • конструктор набуває ресурс (зателефонувавши)new якщо ресурс є виділенням пам'яті)
  • деструктор звільняє ресурс,
  • копіювання та присвоєння або перешкоджають (роблячи конструктор копій та операторами призначення приватними), або реалізуються для коректної роботи (наприклад, шляхом клонування базового ресурсу)

Потім цей клас інстанціюється локально, на стеці або як член класу, а не шляхом виклику newта зберігання вказівника.

Вам часто не потрібно визначати ці класи самостійно. Стандартні контейнери бібліотеки також поводяться таким чином, так що будь-який об'єкт, що зберігається у програмі, std::vectorзвільняється при знищенні вектора. Так знову ж , не зберігати покажчик в контейнер (який вимагав би ви назвати newі delete), а , швидше за , об'єкт сам (який дає вам управління пам'яттю безкоштовно ). Так само розумні класи вказівників можна використовувати для легкого загортання об'єктів, які просто повинні бути виділені new, та контролю їхнього життя.

Це означає, що коли об’єкт виходить за межі, він автоматично знищується, а його ресурс звільняється та очищається.

Якщо ви робите це послідовно протягом усього коду, у вас просто не буде витоків пам'яті. Все, що може просочитися, пов'язане з деструктором, який гарантовано буде викликаний, коли контроль покине область, в якій об'єкт був оголошений.


якщо розумний вказівник містить клас і цей клас містить вказівник кількох інших класів. коли розумне вимкнеться, це означає, що всі вказівники всередині буде видалено безпечно.
Chris_vr

@Chris: Припустимо що об’єкт, на який вказує інтелектуальний покажчик, має деструктор, який робить необхідну очистку, або об'єкт містить членів, які самі мають деструктори для проведення необхідного очищення. По суті, доки кожен об’єкт дбає про себе (очищення після себе, коли він знищений), і поки кожен об'єкт зберігається за значенням, а не як вказівник, тоді все, що потрібно звільнити , звільниться.
jalf

3

AddressSanitizer (ASan) - швидкий детектор помилок пам'яті. Він виявляє помилки переповнення буферів без використання та {heap, stack, global} -програми в програмах C / C ++. Він знаходить:

  • Використовувати після безкоштовного (вивішування вказівника)
  • Переповнення буфера
  • Переповнення буфера стека
  • Переповнення глобального буфера
  • Використовувати після повернення
  • Помилки порядку ініціалізації

Цей інструмент дуже швидкий. Середнє уповільнення інструментальної програми становить ~ 2x.


Дивіться особливо LeakSanitizer
Габріель

0

На додаток до інструментів та методів, що надаються в інших підказках, інструменти для аналізу статичного коду можуть бути використані для виявлення витоків пам'яті (та інших проблем). Безкоштовним надійним інструментом є Cppcheck. Але є маса інших інструментів. У Вікіпедії є список інструментів аналізу статичного коду.


-1

Переконайтесь, що вся пам'ять купи успішно звільнена. Немає потреби, якщо ви ніколи не розподіляєте пам’ять на купу. Якщо ви це робите, підраховуйте кількість разів, коли ви обмежуєте пам'ять, і підраховуйте кількість часу, коли ви звільняєте пам'ять.


-3

Ні "новий", ні "видалити" ніколи не повинні використовуватися в коді програми. Натомість створіть новий тип, який використовує ідіому менеджера / працівника, в якому клас менеджера виділяє та звільняє пам’ять та передає всі інші операції об'єкту робітника.

На жаль, це більше роботи, ніж має бути, оскільки C ++ не має перевантаження "оператора". Це ще більше роботи за наявності поліморфізму.

Але це варте зусиль, оскільки тоді вам ніколи не доведеться турбуватися про витоки пам’яті, а значить, навіть не потрібно їх шукати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.