2010-07-24 5 views
8

Prawdopodobnie każdy wpadł na ten problem przynajmniej raz w trakcie rozwoju:Jak uniknąć "zamiany śmierci" podczas rozwoju?

while(/*some condition here that somehow never will be false*/) 
{ 
    ... 
    yourvector.push_back(new SomeType()); 
    ... 
} 

Jak widać program uruchamia się spuszczenie całej pamięci systemowej, zawieszenia się programu i system zaczyna zamieniać się jak szalony. Jeśli nie rozpoznasz problemu wystarczająco szybko i zabijesz proces, prawdopodobnie dostaniesz nieodpowiedzialny system w ciągu kilku sekund, gdy wskaźnik myszy nawet się nie rusza. Możesz albo poczekać na awarię programu z błędem "braku pamięci" (co może zająć kilka długich minut), albo na zresetowaniu komputera.

Jeśli nie można wyśledzić błąd natychmiast potem trzeba będzie kilka testów i resetuje się dowiedzieć, co jest bardzo irytujące ...

szukam sposób możliwie wielu platformach, aby zapobiec jakoś. Najlepszy byłby kod trybu debugowania, który kończy program, jeśli przydzielono mu zbyt dużo pamięci, ale w jaki sposób mogę śledzić ilość przydzielonej pamięci? Przesłanianie globalnych operatorów nowych i usuwania nie pomoże, ponieważ funkcja wolna, którą wywołuję w usuwaniu, nie da pojęcia, ile bajtów zostało zwolnionych.

Wszelkie pomysły doceniane.

+1

Jakiego systemu operacyjnego używasz? Ile pamięci RAM? Jaki jest rozmiar pliku stronicowania? Niezbędne, aby wiedzieć. –

+0

Jeśli masz skłonność do tego rodzaju błędów, po prostu wyłącz swap. Mam przed sobą ciężko pracującą maszynę programistyczną z czasem pracy ponad miesiąc. Znak wysokiej wody dla maksymalnej zastosowanej wymiany to 22 MB. (linux 2.6.32 Rdzeń 4GiB, swapiness 60) – msw

Odpowiedz

10

Zastępowanie globalne new i delete operatorzy nie pomoże, bo wolna funkcja chciałbym powołać się w delete nie da żadnego pojęcia, jak wiele bajtów są zwalniane.

Ale możesz to zrobić. Oto pełna ramy przeciążania operatorów pamięci globalnej (wyrzucić go w jakiś global_memory.cpp pliku):

namespace 
{ 
    // utility 
    std::new_handler get_new_handler(void) 
    { 
     std::new_handler handler = std::set_new_handler(0); 
     std::set_new_handler(handler); 

     return handler; 
    } 

    // custom allocation scheme goes here! 
    void* allocate(std::size_t pAmount) 
    { 

    } 

    void deallocate(void* pMemory) 
    { 

    } 

    // allocate with throw, properly 
    void* allocate_throw(std::size_t pAmount) 
    { 
     void* result = allocate(pAmount); 

     while (!result) 
     { 
      // call failure handler 
      std::new_handler handler = get_new_handler(); 
      if (!handler) 
      { 
       throw std::bad_alloc(); 
      } 

      handler(); 

      // try again 
      result = allocate(pAmount); 
     } 

     return result; 
    } 
} 

void* operator new(std::size_t pAmount) throw(std::bad_alloc) 
{ 
    return allocate_throw(pAmount); 
} 

void *operator new[](std::size_t pAmount) throw(std::bad_alloc) 
{ 
    return allocate_throw(pAmount); 
} 

void *operator new(std::size_t pAmount, const std::nothrow_t&) throw() 
{ 
    return allocate(pAmount); 
} 

void *operator new[](std::size_t pAmount, const std::nothrow_t&) throw() 
{ 
    return allocate(pAmount); 
} 

void operator delete(void* pMemory) throw() 
{ 
    deallocate(pMemory); 
} 

void operator delete[](void* pMemory) throw() 
{ 
    deallocate(pMemory); 
} 

void operator delete(void* pMemory, const std::nothrow_t&) throw() 
{ 
    deallocate(pMemory); 
} 

void operator delete[](void* pMemory, const std::nothrow_t&) throw() 
{ 
    deallocate(pMemory); 
} 

Następnie można zrobić coś takiego:

// custom allocation scheme goes here! 
    const std::size_t allocation_limit = 1073741824; // 1G 
    std::size_t totalAllocation = 0; 

    void* allocate(std::size_t pAmount) 
    { 
     // make sure we're within bounds 
     assert(totalAllocation + pAmount < allocation_limit); 

     // over allocate to store size 
     void* mem = std::malloc(pAmount + sizeof(std::size_t)); 
     if (!mem) 
      return 0; 

     // track amount, return remainder 
     totalAllocation += pAmount; 
     *static_cast<std::size_t*>(mem) = pAmount; 

     return static_cast<char*>(mem) + sizeof(std::size_t); 
    } 

    void deallocate(void* pMemory) 
    { 
     // get original block 
     void* mem = static_cast<char*>(pMemory) - sizeof(std::size_t); 

     // track amount 
     std::size_t amount = *static_cast<std::size_t*>(mem); 
     totalAllocation -= pAmount; 

     // free 
     std::free(mem); 
    } 
+0

Przechowywanie rozmiaru na przydzielonym miejscu jest dobrym pomysłem. Dlaczego nie przyszło mi to do głowy ... Również +1 za wskazanie, że powinienem zastąpić nowe i nie rzucające nowe wersje i całkowicie je zapomniałem. :) – Calmarius

1

ponieważ wolne funkcja chciałbym powołać się w delete nie da żadnego pojęcia, jak wiele bajtów są zwalniane

może on, to po prostu trzeba zachować mapę wielkości przydzielonych pamięci według adresu i odejmij odpowiednią ilość na podstawie tych informacji w czasie wolnym.

+0

Mapa STL użyłaby globalnego nowego i też usunie, chyba że dasz mu swój własny przydział, niezależny od globalnego. Widzę możliwość nieskończonej rekurencji. – Calmarius

0

Można realizować jesteś właścicielem globalnego operatora new:

void* operator new (std::size_t size) throw (std::bad_alloc); 
void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw(); 
void* operator new (std::size_t size, void* ptr) throw(); 

void* operator new[] (std::size_t size) throw (std::bad_alloc); 
void* operator new[] (std::size_t size, const std::nothrow_t& nothrow_constant) throw(); 
void* operator new[] (std::size_t size, void* ptr) throw(); 

Następnie wystarczy ustawić sztywny limit o ile pamięci można przydzielić; może nawet jak moch Kb/sec

13

Jeśli używasz systemu Linux lub Unix-owski, możesz sprawdzić w setrlimit(2), który pozwala skonfigurować limity zasobów dla twojego programu. Możesz zrobić podobne rzeczy z powłoki z ulimit.

+0

+1, ponieważ jest to kolejna rzecz, o której nie wiedziałem. Ale chcę, aby mój program był platformą krzyżową, więc czasami rozwijam go pod Windows czasami pod Linuksem, a mój problem może wystąpić po obu stronach. – Calmarius

0

Jeśli chcesz łatwym sposobem na znalezienie wszystkich tych, potencjalne przecieki, po prostu użyj edytora tekstu i wyszukaj .push_back w całym kodzie źródłowym. Następnie sprawdź wszystkie wystąpienia tego wywołania funkcji i zobacz, czy znajdują się one wewnątrz ciasnej pętli. To może pomóc w znalezieniu złych problemów w kodzie. Pewnie możesz otrzymać 100 trafień, ale to może być zbadane w skończonym czasie. Lub możesz napisać analizator statyczny (używając API Scitools), aby znaleźć wszystkie pętle while, które mają metodę kontenerową o nazwie .push_back, która jest nazywana wewnątrz nich.