Як відстежити помилку "подвійний вільний або корупційний"


94

Коли я запускаю свою програму (C ++), вона виходить з ладу з цією помилкою.

* виявлено glibc * ./load: подвійна безкоштовна або корупція (! попередня): 0x0000000000c6ed50 ***

Як я можу виявити помилку?

Я спробував використовувати std::coutоператори print ( ), але безуспішно. Чи може gdbце полегшити?


5
Цікаво, чому всі пропонують NULLпокажчики (що маскує помилки, які в іншому випадку вловлюються, як це прекрасно показує це питання), але ніхто не пропонує просто взагалі не робити ручне управління пам’яттю, що дуже добре можливо в С ++. Я deleteроками не писав . (І так, мій код є критично важливим для продуктивності. Інакше його не писали б на C ++.)
sbi

2
@sbi: Корупція в купі та подібні речі рідко трапляються, принаймні не там, де вони трапляються. NULLвказівники можуть спричинити збій програми раніше.
Гастуркун

@Hasturkun: Я категорично не згоден. Основним стимулом NULLпокажчиків є запобігання delete ptr;вибуху секунди - що маскує помилку, оскільки ця секунда deleteніколи не мала відбуватися. (Він також використовується для перевірки, чи покажчик все ще вказує на дійсний об'єкт. Але це просто піднімає питання, чому у вас є вказівник в області дії, який не має об'єкта, на який вказувати.)
sbi

Відповіді:


64

Якщо ви використовуєте glibc, ви можете встановити для MALLOC_CHECK_змінної середовища значення 2, це призведе до того, що glibc використовуватиме версію malloc, яка допускає помилки , що призведе до того, що ваша програма перерветься в точці, коли буде зроблено подвійне вільне.

Ви можете встановити це з gdb, використовуючи set environment MALLOC_CHECK_ 2команду перед запуском програми; програма повинна перервати, при цьому free()виклик буде видно у зворотній трасі.

див. сторінкуmalloc() довідки для отримання додаткової інформації


2
Налаштування MALLOC_CHECK_2фактично виправило мою подвійну безкоштовну проблему (хоча це не виправляє, якщо вона лише у режимі налагодження)
puk

4
@puk У мене така сама проблема, якщо встановити MALLOC_CHECK_ на 2, я уникаю моєї подвійної проблеми. Які ще варіанти можна вводити менше, ніж код, щоб відтворити проблему та забезпечити зворотну трасування?
Вей Чжун,

Також мати його там, де налаштування MALLOC_CHECK_ дозволяє уникнути проблеми. Колеги-коментатори / хто-небудь ... Ви знайшли інший спосіб проявити проблему?
pellucidcoder

"Коли для MALLOC_CHECK_ встановлено ненульове значення, використовується спеціальна (менш ефективна) реалізація, яка призначена толерантною до простих помилок, таких як подвійні дзвінки free з тим самим аргументом, або перевищення одного байта (off -by-one помилки). " gnu.org/software/libc/manual/html_node/ ... Отже, схоже, MALLOC_CHECK_ використовується лише для уникнення простих помилок пам'яті, а не для їх виявлення.
pellucidcoder

Насправді .... support.microfocus.com/kb/doc.php?id=3113982 здається, що встановлення MALLOC_CHECK_ на 3 є найбільш корисним і може використовуватися для виявлення помилок.
pellucidcoder

32

Є принаймні дві можливі ситуації:

  1. ви двічі видаляєте одну й ту саму сутність
  2. ви видаляєте те, що не було виділено

Для першого я настійно рекомендую NULL-ing всіх видалених покажчиків.

У вас є три варіанти:

  1. перевантажувати нові та видаляти та відстежувати розподіли
  2. так, використовуйте gdb - тоді ви отримаєте зворотний шлях від аварії, і це, мабуть, буде дуже корисно
  3. як запропоновано - використовуйте Valgrind - потрапити в нього непросто, але це заощадить ваш час у тисячу разів у майбутньому ...

2. спричинить корупцію, але я не думаю, що це повідомлення зазвичай з'являється, оскільки перевірка осудності виконується лише в купі. Однак я думаю, що переповнення буфера кучі 3. можливе.
Метью Флашен

Хороший. Правда, я пропустив зробити покажчик NULL і зіткнувся з цією помилкою. Уроки вивчені!
hrushi

26

Ви можете використовувати gdb, але спочатку я спробував би Valgrind . Див . Посібник із швидкого запуску .

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


1
@SMR, у цьому випадку суттєвою частиною відповіді є ціла, велика, пов'язана сторінка. Тож включити лише посилання у відповідь цілком нормально. Кілька слів про те, чому автор віддає перевагу Valgrind перед gdb та як би він вирішив конкретну проблему, - це IMHO, чого насправді не вистачає у відповіді.
ndemou

20

Три основні правила:

  1. Встановити вказівник на NULL після вільного
  2. Перевірити для NULL перед звільненням.
  3. Ініціалізація вказівника на NULLпочатку.

Поєднання цих трьох робіт досить добре.


1
Я не фахівець з питань С, але зазвичай можу тримати голову над водою. Чому No1? Це просто для того, щоб ваша програма припиняла збій при спробі отримати доступ до вільного вказівника, а не просто мовчазною помилкою?
Даніель Хармс,

1
@Precision: Так, справа в цьому. Це хороша практика: наявність вказівника на видалену пам’ять - це ризик.
раніше

10
Власне кажучи, я вважаю, що №2 непотрібна, оскільки більшість компіляторів дозволять вам спробувати видалити нульовий покажчик, не викликаючи проблем. Упевнений, хтось виправить мене, якщо я помиляюся. :)
Компонент 10

11
@ Component10 Я думаю, що звільнення NULL вимагає стандарт C, щоб нічого не робити.
Демі

2
@Demetri: Так, ти маєш рацію, "якщо значення операнда видалення є нульовим покажчиком, операція не впливає". (ISO / IEC 14882: 2003 (E) 5.3.5.2)
Компонент 10

15

Ви можете використовувати його valgrindдля налагодження.

#include<stdio.h>
#include<stdlib.h>

int main()
{
 char *x = malloc(100);
 free(x);
 free(x);
 return 0;
}

[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ ./t1
*** glibc detected *** ./t1: double free or corruption (top): 0x00000000058f7010 ***
======= Backtrace: =========
/lib64/libc.so.6[0x3a3127245f]
/lib64/libc.so.6(cfree+0x4b)[0x3a312728bb]
./t1[0x400500]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x3a3121d994]
./t1[0x400429]
======= Memory map: ========
00400000-00401000 r-xp 00000000 68:02 30246184                           /home/sand/testbox/t1
00600000-00601000 rw-p 00000000 68:02 30246184                           /home/sand/testbox/t1
058f7000-05918000 rw-p 058f7000 00:00 0                                  [heap]
3a30e00000-3a30e1c000 r-xp 00000000 68:03 5308733                        /lib64/ld-2.5.so
3a3101b000-3a3101c000 r--p 0001b000 68:03 5308733                        /lib64/ld-2.5.so
3a3101c000-3a3101d000 rw-p 0001c000 68:03 5308733                        /lib64/ld-2.5.so
3a31200000-3a3134e000 r-xp 00000000 68:03 5310248                        /lib64/libc-2.5.so
3a3134e000-3a3154e000 ---p 0014e000 68:03 5310248                        /lib64/libc-2.5.so
3a3154e000-3a31552000 r--p 0014e000 68:03 5310248                        /lib64/libc-2.5.so
3a31552000-3a31553000 rw-p 00152000 68:03 5310248                        /lib64/libc-2.5.so
3a31553000-3a31558000 rw-p 3a31553000 00:00 0
3a41c00000-3a41c0d000 r-xp 00000000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
3a41c0d000-3a41e0d000 ---p 0000d000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
3a41e0d000-3a41e0e000 rw-p 0000d000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
2b1912300000-2b1912302000 rw-p 2b1912300000 00:00 0
2b191231c000-2b191231d000 rw-p 2b191231c000 00:00 0
7ffffe214000-7ffffe229000 rw-p 7ffffffe9000 00:00 0                      [stack]
7ffffe2b0000-7ffffe2b4000 r-xp 7ffffe2b0000 00:00 0                      [vdso]
ffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0                  [vsyscall]
Aborted
[sand@PS-CNTOS-64-S11 testbox]$


[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck ./t1
==20859== Memcheck, a memory error detector
==20859== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20859== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20859== Command: ./t1
==20859==
==20859== Invalid free() / delete / delete[]
==20859==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20859==    by 0x4004FF: main (t1.c:8)
==20859==  Address 0x4c26040 is 0 bytes inside a block of size 100 free'd
==20859==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20859==    by 0x4004F6: main (t1.c:7)
==20859==
==20859==
==20859== HEAP SUMMARY:
==20859==     in use at exit: 0 bytes in 0 blocks
==20859==   total heap usage: 1 allocs, 2 frees, 100 bytes allocated
==20859==
==20859== All heap blocks were freed -- no leaks are possible
==20859==
==20859== For counts of detected and suppressed errors, rerun with: -v
==20859== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$


[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck --leak-check=full ./t1
==20899== Memcheck, a memory error detector
==20899== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20899== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20899== Command: ./t1
==20899==
==20899== Invalid free() / delete / delete[]
==20899==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20899==    by 0x4004FF: main (t1.c:8)
==20899==  Address 0x4c26040 is 0 bytes inside a block of size 100 free'd
==20899==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20899==    by 0x4004F6: main (t1.c:7)
==20899==
==20899==
==20899== HEAP SUMMARY:
==20899==     in use at exit: 0 bytes in 0 blocks
==20899==   total heap usage: 1 allocs, 2 frees, 100 bytes allocated
==20899==
==20899== All heap blocks were freed -- no leaks are possible
==20899==
==20899== For counts of detected and suppressed errors, rerun with: -v
==20899== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$

Одне з можливих виправлень:

#include<stdio.h>
#include<stdlib.h>

int main()
{
 char *x = malloc(100);
 free(x);
 x=NULL;
 free(x);
 return 0;
}

[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ ./t1
[sand@PS-CNTOS-64-S11 testbox]$

[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck --leak-check=full ./t1
==20958== Memcheck, a memory error detector
==20958== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20958== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20958== Command: ./t1
==20958==
==20958==
==20958== HEAP SUMMARY:
==20958==     in use at exit: 0 bytes in 0 blocks
==20958==   total heap usage: 1 allocs, 1 frees, 100 bytes allocated
==20958==
==20958== All heap blocks were freed -- no leaks are possible
==20958==
==20958== For counts of detected and suppressed errors, rerun with: -v
==20958== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$

Перегляньте блог про використання Valgrind Link


Для запуску моєї програми потрібно близько 30 хвилин, а на Valgrind це може зайняти від 18 до 20 годин.
Kemin Zhou

12

За допомогою сучасних компіляторів C ++ ви можете використовувати дезінфікуючі засоби для відстеження.

Приклад прикладу:

Моя програма:

$cat d_free.cxx 
#include<iostream>

using namespace std;

int main()

{
   int * i = new int();
   delete i;
   //i = NULL;
   delete i;
}

Компіляція дезінфікуючих засобів для адрес:

# g++-7.1 d_free.cxx -Wall -Werror -fsanitize=address -g

Виконати:

# ./a.out 
=================================================================
==4836==ERROR: AddressSanitizer: attempting double-free on 0x602000000010 in thread T0:
    #0 0x7f35b2d7b3c8 in operator delete(void*, unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140
    #1 0x400b2c in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:11
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)
    #3 0x400a08  (/media/sf_shared/jkr/cpp/d_free/a.out+0x400a08)

0x602000000010 is located 0 bytes inside of 4-byte region [0x602000000010,0x602000000014)
freed by thread T0 here:
    #0 0x7f35b2d7b3c8 in operator delete(void*, unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140
    #1 0x400b1b in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:9
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)

previously allocated by thread T0 here:
    #0 0x7f35b2d7a040 in operator new(unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:80
    #1 0x400ac9 in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:8
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)

SUMMARY: AddressSanitizer: double-free /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140 in operator delete(void*, unsigned long)
==4836==ABORTING

Щоб дізнатись більше про дезінфікуючі засоби, ви можете перевірити цю чи цю або будь-яку сучасну документацію компіляторів C ++ (наприклад, gcc, clang тощо).


5

Чи використовуєте ви розумні покажчики, такі як Boost shared_ptr? Якщо так, перевірте, чи ви прямо використовуєте де-небудь сирий вказівник, зателефонувавшиget() . Я виявив, що це досить поширена проблема.

Наприклад, уявіть сценарій, коли необроблений вказівник передається (можливо, як обробник зворотного виклику, скажімо) до вашого коду. Ви можете вирішити призначити це розумному вказівнику, щоб впоратися з підрахунком посилань тощо. Велика помилка: ваш код не є власником цього вказівника, якщо ви не зробите глибоку копію. Коли ваш код закінчується за допомогою розумного вказівника, він знищить його та спробує знищити пам’ять, на яку він вказує, оскільки вважає, що це нікому не потрібно, але викличний код спробує видалити його, і ви отримаєте подвійний безкоштовна проблема.

Звичайно, це може бути не ваша проблема тут. Найпростіше, ось приклад, який показує, як це може статися. Перше видалення - це нормально, але компілятор відчуває, що ця пам’ять вже видалено і викликає проблему. Ось чому присвоєння покажчику 0 відразу після видалення є гарною ідеєю.

int main(int argc, char* argv[])
{
    char* ptr = new char[20];

    delete[] ptr;
    ptr = 0;  // Comment me out and watch me crash and burn.
    delete[] ptr;
}

Редагувати: змінено deleteна delete[], оскільки ptr - це масив символів.


Я не використовував жодної команди видалення в своїй програмі. Чи може це все-таки проблема?
невромант

1
@Phenom: Чому ви не використовували видалення? Це тому, що ви використовуєте розумні вказівники? Імовірно, ви використовуєте нове у своєму коді для створення об'єктів у купі? Якщо відповідь на обидва ці питання так, то чи використовуєте ви get / set на розумних покажчиках для копіювання навколо необроблених покажчиків? Якщо так, то не робіть! Ви б порушили підрахунок посилань. Крім того, ви можете призначити покажчик з коду бібліотеки, який ви викликаєте, на розумний вказівник. Якщо ви не володієте пам'яттю, на яку вказали, тоді не робіть цього, оскільки і бібліотека, і розумний вказівник намагатимуться видалити її.
Компонент 10

-2

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

Що закриває файл, який ви вже закрили.

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


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