2010-05-13 10 views
5

Napisałem następujący debugger sterty w celu zademonstrowania wycieków pamięci, podwójnych usunięć i błędnych form usuwania (tj. Próby usunięcia tablicy z delete p zamiast delete[] p) dla początkujących programistów.Critique my heap debugger

Chciałbym uzyskać informacje zwrotne od silnych programistów C++, ponieważ nigdy wcześniej tego nie robiłem i jestem pewien, że popełniłem głupie błędy. Dzięki!

#include <cstdlib> 
#include <iostream> 
#include <new> 

namespace 
{ 
    const int ALIGNMENT = 16; 
    const char* const ERR = "*** ERROR: "; 
    int counter = 0; 

    struct heap_debugger 
    { 
     heap_debugger() 
     { 
      std::cerr << "*** heap debugger started\n"; 
     } 

     ~heap_debugger() 
     { 
      std::cerr << "*** heap debugger shutting down\n"; 
      if (counter > 0) 
      { 
       std::cerr << ERR << "failed to release memory " << counter << " times\n"; 
      } 
      else if (counter < 0) 
      { 
       std::cerr << ERR << (-counter) << " double deletes detected\n"; 
      } 
     } 
    } instance; 

    void* allocate(size_t size, const char* kind_of_memory, size_t token) throw (std::bad_alloc) 
    { 
     void* raw = malloc(size + ALIGNMENT); 
     if (raw == 0) throw std::bad_alloc(); 

     *static_cast<size_t*>(raw) = token; 
     void* payload = static_cast<char*>(raw) + ALIGNMENT; 

     ++counter; 
     std::cerr << "*** allocated " << kind_of_memory << " at " << payload << " (" << size << " bytes)\n"; 
     return payload; 
    } 

    void release(void* payload, const char* kind_of_memory, size_t correct_token, size_t wrong_token) throw() 
    { 
     if (payload == 0) return; 

     std::cerr << "*** releasing " << kind_of_memory << " at " << payload << '\n'; 
     --counter; 

     void* raw = static_cast<char*>(payload) - ALIGNMENT; 
     size_t* token = static_cast<size_t*>(raw); 

     if (*token == correct_token) 
     { 
      *token = 0xDEADBEEF; 
      free(raw); 
     } 
     else if (*token == wrong_token) 
     { 
      *token = 0x177E6A7; 
      std::cerr << ERR << "wrong form of delete\n"; 
     } 
     else 
     { 
      std::cerr << ERR << "double delete\n"; 
     } 
    } 
} 

void* operator new(size_t size) throw (std::bad_alloc) 
{ 
    return allocate(size, "non-array memory", 0x5AFE6A8D); 
} 

void* operator new[](size_t size) throw (std::bad_alloc) 
{ 
    return allocate(size, " array memory", 0x5AFE6A8E); 
} 

void operator delete(void* payload) throw() 
{ 
    release(payload, "non-array memory", 0x5AFE6A8D, 0x5AFE6A8E); 
} 

void operator delete[](void* payload) throw() 
{ 
    release(payload, " array memory", 0x5AFE6A8E, 0x5AFE6A8D); 
} 

Odpowiedz

4

Zamiast robić uciążliwe notatki, możesz zachować listę wszystkich przydziałów. Następnie możesz zwolnić pamięć bez niszczenia własnych danych i śledzić, ile razy dany adres jest "usuwany", a także znaleźć miejsca, w których program próbuje usunąć niepasujący adres (tj. Nie ma go na liście).

+0

Jak dokładnie mogę się upewnić, że lista notatek nie śledzi własnej pamięci? Napisz alokator? Napisz własną implementację listy na podstawie malloc i za darmo? – fredoverflow

+0

Moja pierwsza implementacja nieinwazyjnego debugera sterty znajduje się tutaj (http://stackoverflow.com/questions/2835416/critique-my-non-intrusive-heap-debugger). – fredoverflow

1
void* raw = static_cast<char*>(payload) - ALIGNMENT; 

Jeśli payload został już usunięty, nie sprawiają, że ten niezdefiniowanej zachowanie?

+0

Szczerze mówiąc, nie wiem. Mam nadzieję, że kiedy wskaźnik został przekazany przez "nowego" w pewnym momencie w przeszłości, ponowne użycie go nie spowoduje, że zbyt wiele demonów wyleci z mojego nosa. Ale nie jestem pewien. – fredoverflow

+0

To znaczy, nie widzę, jak możesz mieć nadzieję sprawdzić, czy otrzymałeś prawidłowy wskaźnik, jeśli całkowicie polegasz na tym, że wskaźnik jest ważny (albo nosowe demony * będą latać). Myślę, że możesz chcieć ponownie rozważyć odpowiedź bitc ... – UncleBens

1

Nie stosuję bardzo dobrze stałych stałych/stałych ciągów znaków - umieść je w enumie? I naprawdę nie mam pojęcia o tokenie całkiem dobrze. Potrzebuje więcej komentarzy.

+0

Ah, masz na myśli tokeny i elementy "tablicowe/nieszablonowe". Tak, to ma sens. Dzięki. – fredoverflow

2

To naprawdę świetny początek. Oto 2 centy, ponieważ poprosiłeś o opinię:

  1. Kod zapisuje informacje o śledzeniu do cerr, co tak naprawde jest spowodowane błędami. Użyj cout dla dzienników informacyjnych.
  2. Kwota PRZEPLOTOWOŚĆ jest dowolna. Jeśli kod próbował przydzielić 4090 bajtów, przydzielono by 4106, który rozlewa się do następnego bloku 4k, który jest wielkości strony pamięci. Obliczona wartość wyrównania byłaby lepsza ... lub zmień nazwę ALIGNMENT jako HEADER_SIZE lub coś podobnego.
  3. Biorąc pod uwagę tworzony nagłówek, można zapisać rozmiar i flagę dla "rodzaju pamięci" w czasie przydzielania i porównać ją w czasie zwolnienia.
  4. Token powinien prawdopodobnie być nazywany "wartownikiem" lub "wartością kanarkową".
  5. Dlaczego token to size_t? Dlaczego nie tylko pustka *?
  6. Twój czek na wydanie zerowe powinien prawdopodobnie spowodować wyjątek - czy nie byłby to błąd, gdyby kod usunął zerowy wskaźnik?
  7. Twoje wartości "correct_token" i "wrong_token" są zbyt podobne. Musiałem ponownie przeczytać kod, żeby się upewnić.
  8. Biorąc pod uwagę punkt (3), można podwoić kwotę, którą dodatkowo przydzielasz i masz przed i po wartownikach/blokach wartowników. To wykryłoby niedopełnienie pamięci i przekroczenie.
+0

Można bezpiecznie usunąć wskaźnik zerowy. – bitc

+1

2.Jak obliczyć wartość, która działa dla każdego typu? | 5. Nie rozumiem, dlaczego stała wartość integralna powinna być "void *", proszę wyjaśnij. | 6. Nie, usuń 0 musi być odgłos. Zgłoszenie wyjątku naruszałoby umowę usuwania. | 7. Tak, dziękuję za tę obserwację. | 8. Chociaż nie jest to mój pierwotny cel, prawdopodobnie jest to nadal dobry pomysł. Dzięki! – fredoverflow

+1

1. A może 'zapchać'? Myślę, że chodzi o to, aby móc przekierować wyjście dziennika. – UncleBens

2

Wyjaśnij, dlaczego wybrałeś "ALIGNMENT" jako identyfikator. Wyjaśnij, dlaczego wybrałeś 16. Wyjaśnij, jak algorytm łapie najczęstsze błędy, np. Przepełnienie końca bloku przydzielonego sterty lub zapomnienie o zwolnieniu pamięci.

+0

Masz na myśli "wyjaśnij" lub "dodaj komentarze do kodu"? Wybrałem opcję "ALIGNMENT", ponieważ alokacja pamięci dla dowolnych obiektów musi być wyrównana o pewną liczbę, która działa dla każdego typu. Wybrałem 16, ponieważ nie znam przenośnego sposobu ustalania rzeczywistej wartości, więc mile widziane są lepsze sugestie :) Przepełnienie bufora w ogóle nie jest objęte moim debuggerem sterty. Zapomnienie o zwolnieniu pamięci objęte jest sprawdzaniem licznika, który jest inkrementowany przy każdym wykonywanym 'nowym' i zmniejszany o każdy wykonany' delete', patrz kod w '~ heap_debugger'. – fredoverflow