Comment détecter une erreur «double libre ou corruption»

92

Lorsque j'exécute mon programme (C ++), il se bloque avec cette erreur.

* glibc détecté * ./load: double libre ou corruption (! prev): 0x0000000000c6ed50 ***

Comment puis-je retrouver l'erreur?

J'ai essayé d'utiliser les std::coutinstructions print ( ), sans succès. Pourrait gdbrendre cela plus facile?

neuromancien
la source
5
Je me demande pourquoi tout le monde suggère des NULLpointeurs (qui masquent les erreurs qui sont autrement capturées, comme le montre bien cette question), mais personne ne suggère de ne pas faire du tout de gestion manuelle de la mémoire, ce qui est très bien possible en C ++. Je n'ai pas écrit deletedepuis des années. (Et, oui, mon code est critique pour les performances. Sinon, il n'aurait pas été écrit en C ++.)
sbi
2
@sbi: La corruption de tas et autres sont rarement détectées, du moins pas là où elles se produisent. NULLdes pointeurs peuvent faire planter votre programme plus tôt.
Hasturkun
@Hasturkun: Je ne suis pas du tout d'accord. Une incitation majeure aux NULLpointeurs est d'empêcher une seconde delete ptr;d'exploser - ce qui masque une erreur, car cette seconde deleten'aurait jamais dû se produire. (Il est également utilisé pour vérifier si un pointeur pointe toujours vers un objet valide. Mais cela soulève simplement la question de savoir pourquoi vous avez un pointeur dans la portée qui n'a pas d'objet vers
lequel

Réponses:

64

Si vous utilisez la glibc, vous pouvez définir la MALLOC_CHECK_variable d'environnement sur 2, cela obligera la glibc à utiliser une version tolérante aux erreurs demalloc , ce qui entraînera l'abandon de votre programme au moment où le double free est effectué.

Vous pouvez définir cela à partir de gdb en utilisant la set environment MALLOC_CHECK_ 2commande avant d'exécuter votre programme; le programme doit abandonner, l' free()appel étant visible dans la trace arrière.

voir la page de manuelmalloc() pour plus d'informations

Hasturkun
la source
2
Le réglage a en MALLOC_CHECK_2fait résolu mon problème de double gratuit (bien qu'il ne soit pas résolu s'il est en mode débogage uniquement)
puk
4
@puk J'ai le même problème, définir MALLOC_CHECK_ sur 2 évite mon problème de double free. Quelles sont les autres options pour injecter moins que du code pour reproduire le problème et fournir une trace arrière?
Wei Zhong
Ayez-le également là où le réglage MALLOC_CHECK_ évite le problème. Amis commentateurs / quelqu'un ... avez-vous trouvé une manière différente d'exposer le problème?
pellucidcoder
"Lorsque MALLOC_CHECK_ est défini sur une valeur différente de zéro, une implémentation spéciale (moins efficace) est utilisée, conçue pour être tolérante contre les erreurs simples, telles que les appels doubles de free avec le même argument, ou les dépassements d'un seul octet (désactivé -par-un bogues). " gnu.org/software/libc/manual/html_node/... Il semble donc que MALLOC_CHECK_ ne soit utilisé que pour éviter de simples erreurs de mémoire, pas pour les détecter.
pellucidcoder
En fait .... support.microfocus.com/kb/doc.php?id=3113982 semble que définir MALLOC_CHECK_ sur 3 est le plus utile et peut être utilisé pour détecter les erreurs.
pellucidcoder
32

Il existe au moins deux situations possibles:

  1. vous supprimez deux fois la même entité
  2. vous supprimez quelque chose qui n'a pas été attribué

Pour le premier, je suggère fortement NULL-ing tous les pointeurs supprimés.

Vous avez trois options:

  1. surcharger nouveau et supprimer et suivre les allocations
  2. oui, utilisez gdb - alors vous obtiendrez une trace de votre plantage, et ce sera probablement très utile
  3. comme suggéré - utilisez Valgrind - ce n'est pas facile à entrer, mais cela vous fera gagner du temps mille fois à l'avenir ...
Kornel Kisielewicz
la source
2. causerait une corruption, mais je ne pense pas que ce message apparaîtrait généralement, car la vérification de l'intégrité est uniquement effectuée sur le tas. Cependant, je pense que 3. le dépassement de la mémoire tampon du tas est possible.
Matthew Flaschen
Bon. Vrai j'ai manqué de rendre le pointeur NULL et face à cette erreur. Leçons apprises!
hrushi
26

Vous pouvez utiliser gdb, mais j'essaierais d'abord Valgrind . Consultez le guide de démarrage rapide .

En bref, Valgrind instrumente votre programme afin qu'il puisse détecter plusieurs types d'erreurs dans l'utilisation de la mémoire allouée dynamiquement, comme les doubles libérations et les écritures au-delà de la fin des blocs de mémoire alloués (ce qui peut corrompre le tas). Il détecte et signale les erreurs dès qu'elles se produisent , vous indiquant ainsi directement la cause du problème.

Matthew Flaschen
la source
1
@SMR, dans ce cas, les parties essentielles de la réponse sont l'ensemble, grande page liée. Donc, inclure uniquement le lien dans la réponse est parfaitement bien. Quelques mots sur les raisons pour lesquelles l'auteur préfère Valgrind à gdb et comment il aborderait le problème spécifique est à mon humble avis ce qui manque vraiment à la réponse.
ndemou
20

Trois règles de base:

  1. Définir le pointeur sur NULL après libre
  2. Vérifier NULL avant de libérer.
  3. Initialisez le pointeur au NULLdébut.

La combinaison de ces trois fonctionne assez bien.

Jack
la source
1
Je ne suis pas un expert en C, mais je peux généralement garder la tête hors de l'eau. Pourquoi n ° 1? Est-ce juste pour que votre programme se bloque complètement lorsque vous essayez d'accéder à un pointeur gratuit, et pas seulement à une erreur silencieuse?
Daniel Harms
1
@Precision: Oui, c'est le but. C'est une bonne pratique: avoir un pointeur vers la mémoire supprimée est un risque.
ereOn
10
À proprement parler, je pense que le n ° 2 n'est pas nécessaire car la plupart des compilateurs vous permettront d'essayer de supprimer un pointeur nul sans que cela pose un problème. Je suis sûr que quelqu'un me corrigera si je me trompe. :)
Composant 10
11
@ Component10 Je pense que la libération de NULL est requise par le standard C pour ne rien faire.
Demi
2
@Demetri: Oui, vous avez raison "si la valeur de l'opérande de suppression est le pointeur nul l'opération n'a aucun effet." (ISO / CEI 14882: 2003 (F) 5.3.5.2)
Composante 10
15

Vous pouvez utiliser valgrindpour le déboguer.

#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]$

Une solution possible:

#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]$

Consultez le blog sur l'utilisation de Valgrind Link

Sandipan Karmakar
la source
Mon programme prend environ 30 minutes pour s'exécuter, sur Valgrind cela peut prendre de 18 à 20 heures pour se terminer.
Kemin Zhou
11

Avec les compilateurs C ++ modernes, vous pouvez utiliser des désinfectants pour effectuer le suivi.

Exemple d'exemple:

Mon programme:

$cat d_free.cxx 
#include<iostream>

using namespace std;

int main()

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

Compilez avec des désinfectants d'adresse:

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

Exécutez:

# ./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

Pour en savoir plus sur les désinfectants, vous pouvez vérifier ceci ou cela ou n'importe quelle documentation des compilateurs C ++ modernes (par exemple gcc, clang etc.).

Sitesh
la source
5

Utilisez-vous des pointeurs intelligents tels que Boost shared_ptr? Si tel est le cas, vérifiez si vous utilisez directement le pointeur brut n'importe où en appelantget() . J'ai trouvé que c'était un problème assez courant.

Par exemple, imaginez un scénario dans lequel un pointeur brut est passé (peut-être en tant que gestionnaire de rappel, par exemple) à votre code. Vous pourriez décider de l'attribuer à un pointeur intelligent afin de faire face au comptage de références, etc. Grosse erreur: votre code ne possède pas ce pointeur à moins que vous n'en preniez une copie complète. Lorsque votre code est terminé avec le pointeur intelligent, il le détruira et tentera de détruire la mémoire vers laquelle il pointe car il pense que personne d'autre n'en a besoin, mais le code appelant essaiera alors de le supprimer et vous obtiendrez un double problème gratuit.

Bien sûr, ce n'est peut-être pas votre problème ici. Au plus simple, voici un exemple qui montre comment cela peut arriver. La première suppression est correcte, mais le compilateur détecte qu'elle est déjà supprimée de cette mémoire et pose un problème. C'est pourquoi attribuer 0 à un pointeur immédiatement après la suppression est une bonne idée.

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;
}

Edit: changé deleteen delete[], car ptr est un tableau de char.

Composant 10
la source
Je n'ai utilisé aucune commande de suppression dans mon programme. Cela pourrait-il encore être le problème?
neuromancer
1
@Phenom: Pourquoi n'avez-vous pas utilisé de suppressions? Est-ce parce que vous utilisez des pointeurs intelligents? Vous utilisez probablement new dans votre code pour créer des objets sur le tas? Si la réponse à ces deux questions est oui, utilisez-vous get / set sur les pointeurs intelligents pour copier autour des pointeurs bruts? Si oui, ne le faites pas! Vous casseriez le comptage des références. Vous pouvez également affecter un pointeur du code de bibliothèque que vous appelez à un pointeur intelligent. Si vous ne «possédez» pas la mémoire pointée, ne le faites pas, car la bibliothèque et le pointeur intelligent essaieront de la supprimer.
Composant 10
-2

Je sais qu'il s'agit d'un fil de discussion très ancien, mais il s'agit de la principale recherche sur Google pour cette erreur, et aucune des réponses ne mentionne une cause commune de l'erreur.

Ce qui ferme un fichier que vous avez déjà fermé.

Si vous ne faites pas attention et que deux fonctions différentes ferment le même fichier, la seconde générera cette erreur.

Jason
la source
Vous avez tort, cette erreur est renvoyée en raison d'un double libre, exactement comme l'état de l'erreur. Le fait que vous fermez un fichier deux fois entraîne un double libre, car la méthode de fermeture tente clairement de libérer les mêmes données deux fois.
Geoffrey