Как отследить ошибку «двойное освобождение или повреждение»

92

Когда я запускаю свою программу (C ++), она вылетает с этой ошибкой.

* обнаружена glibc * ./load: двойное освобождение или повреждение (! предыдущая): 0x0000000000c6ed50 ***

Как я могу отследить ошибку?

Я безуспешно пытался использовать std::coutоператоры print ( ). Может ли gdbэто сделать проще?

нейромант
источник
5
Интересно, почему все предлагают NULLуказатели (которые маскируют ошибки, которые в противном случае обнаруживаются, как красиво показывает этот вопрос), но никто не предлагает просто вообще не выполнять ручное управление памятью, что очень хорошо возможно в C ++. Я не писал deleteгодами. (И да, мой код критичен к производительности. В противном случае он не был бы написан на C ++.)
SBI
2
@sbi: Повреждение кучи и тому подобное редко обнаруживаются, по крайней мере, там, где они случаются. NULLуказатели могут привести к сбою вашей программы раньше.
Hasturkun
@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 по одному багу) ". 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 для всех удаленных указателей.

У вас есть три варианта:

  1. перегружать новые и удалять и отслеживать распределения
  2. да, используйте gdb - тогда вы получите информацию о сбое, и это, вероятно, будет очень полезно
  3. как предлагалось - используйте Valgrind - в него нелегко войти, но в будущем это сэкономит вам время в тысячу раз ...
Корнель Киселевич
источник
2. может вызвать повреждение, но я не думаю, что это сообщение обычно появляется, поскольку проверка работоспособности выполняется только в куче. Однако, думаю, 3. переполнение буфера кучи возможно.
Мэтью Флашен,
Хороший. Правда, я не сделал указатель NULL и столкнулся с этой ошибкой. Уроки выучены!
hrushi
26

Вы можете использовать gdb, но сначала я бы попробовал Valgrind . См. Краткое руководство .

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

Мэтью Флашен
источник
1
@SMR, в этом случае важная часть ответа - это целая большая страница, на которую есть ссылка. Так что включение только ссылки в ответ - это нормально. Несколько слов о том, почему автор предпочитает Valgrind, а не gdb, и как он будет решать конкретную проблему - это ИМХО, чего действительно не хватает в ответе.
ndemou
20

Три основных правила:

  1. Установить указатель на NULLпосле освобождения
  2. Проверьте NULLперед освобождением.
  3. Инициализировать указатель на NULLв начале.

Комбинация этих трех работает довольно хорошо.

Джек
источник
1
Я не специалист по Си, но обычно могу держать голову над водой. Почему №1? Это просто так, что ваша программа вылетает из строя, когда вы пытаетесь получить доступ к свободному указателю, а не просто к тихой ошибке?
Дэниел Хармс
1
@Precision: Да, в этом суть. Это хорошая практика: наличие указателя на удаленную память - риск.
ereOn
10
Строго говоря, я думаю, что №2 не нужен, так как большинство компиляторов позволяют вам попытаться удалить нулевой указатель, не вызывая проблемы. Я уверен, что меня кто-нибудь поправит, если я ошибаюсь. :)
Компонент 10
11
@ Component10 Я думаю, что освобождение NULL требуется стандартом C, чтобы ничего не делать.
Деми
2
@Demetri: Да, вы правы, «если значение операнда удаления является нулевым указателем, операция не имеет никакого эффекта». (ИСО / МЭК 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 часов.
Кемин Чжоу
11

В современных компиляторах 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 и т. Д.) Документацию.

Sitesh
источник
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 представляет собой массив символов.

Компонент 10
источник
Я не использовал в своей программе никаких команд удаления. Может ли это быть проблемой?
Neuromancer
1
@Phenom: Почему вы не использовали удаления? Это потому, что вы используете умные указатели? Предположительно вы используете новый код в своем коде для создания объектов в куче? Если ответ на оба эти вопроса утвердительный, то используете ли вы get / set для интеллектуальных указателей для копирования необработанных указателей? Если так, то не надо! Вы нарушите подсчет ссылок. В качестве альтернативы вы можете назначить указатель из вызываемого кода библиотеки интеллектуальному указателю. Если вы не владеете указанной памятью, не делайте этого, так как и библиотека, и интеллектуальный указатель попытаются удалить ее.
Компонент 10,
-2

Я знаю, что это очень старый поток, но это самый популярный поиск в Google по этой ошибке, и ни в одном из ответов не упоминается общая причина ошибки.

Это закрывает файл, который вы уже закрыли.

Если вы не обращаете внимания и две разные функции закрывают один и тот же файл, то вторая вызовет эту ошибку.

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