Drobne programowanie

Kaczuś zaprasza do opowieści o algorytmach, językach programowania i strukturach danych

Na stronie stosowane są pliki cookies. Więcej na podstronie.
odsłon: 2381

Kilka słów o używaniu alokatorów.

Zarządzanie pamięcią w takich językach, jak asembler, c, c++ czy pascal, to jedna z podstawowych rzeczy, którymi powinien martwić się programista piszący w jednym z tych języków. Tu można zyskać na efektywności, jak i stracić dość dużo. Dodatkowo, jest to jedno z miejsc odpowiedzialnych za stabilność programów.
Pracując nad różnymi kodami, tak własnymi, jak i cudzymi mam kilka przemysleń na temat przydzielania i zwalniania pamięci. Chciałbym sie z Państwem nimi podzielić, może komuś się to przyda.

1. Czytelny interface programisty


Tworząc zewnętrzne funkcje, które przy okazji przydzielają pamięć, twórzmy funkcje symetryczne zwalniające pamięć. Oczywiście można napisać w dokumentacji, że po zakończeniu pracy zwalniamy pamięć np za pomocą funkcji free. Jednak nie jest to optymalne rozwiązanie, jesli chodzi o rozwój oprogramowania. Może się zdarzyć, że będziemy chcieli w przyszłości zmodyfikować kod (np. okaże się, że w zastosowaniach, gdzie używamy kodu dużo optymalniejsze jest użyć np funkcji typu AllocPool no i wywołanie na tak stworzonym obiekcie funkcji free może spowodować problemy) i poszukiwanie wszystkich obiektów zwalnianych za pomocą free może być problemem.
Tak więc jeśli mamy funkcję przydzielającą pamięć, to robimy dla niej własną funkcję zwalniającą - tak do pary.

2. Czasem warto stworzyć własne alokatory i dealokatory


Przy bibliotekach, które będą mogły być używane w różnych systemach warto robić własne funkcje alokujące i zwalniające pamięć, nawet jeśli byłoby to w niektórych przypadkach tylko #define, albo funkcja inline'owa. Przyczyna tego jest prosta, może się zdażyć, że będziemy potrzebować użyć nasz kod na jakimś małym systemie, w którym są niestandardowe alokatory. Może się również zdarzyć, że będziemy chcieli po prostu zdebugować kod, wpisując wszystkie alokacje i dealokacje do logu.
prosty przykład:
#ifdef MYDEBUG
inline void *MyAlloc(size_t asize)
{
    void *res = NULL;
    kprintf("MyAlloc(%d)\n", asize);
    res = malloc(asize);
    kprintf("MyAlloc result = %p\n", res);
    retyrn res;
}
#else

  #ifdef __MORPHOS__
	inline void *MyAlloc(size_t asize)
	{
	    if (appMessagePool)
    	        return AllocVecPooled(appMessagePool, asize);
    	    else
    		return NULL;
	}
  #else
    #define MyAlloc malloc
  #endif
#endif
oczywiście analogicznie trzeba by stworzyć definicje MyFree.

3. Zwalniać tylko wtedy, gdy trzeba


Kolejny problem, z którym się zetknąłem, to zwalnianie i ponowne alokowanie pamięci. Na przykład:
while (fgo)
{
    llen = GetXSize();
    p = malloc(llen);
    [...]
    free(p);
}
i mamy ciągłą alokację i zwalnianie pamięci, a można przeciez inaczej:
llen_p = 0;

while(fgo)
{
    llen = GetXSize();
    if(llen_p < llen)
    {
    	if(p)
            free(p);
        p = malloc(llen);
        llen_p = llen;
    }
    [...]

}
if (p)
{
    free(p);
    p = NULL;
}
Jak widać, zwalniamy i realokujemy, tylko wtedy, gdy jest to niezbędne. Można też w takim wypadku pomyśleć o stosowaniu jakiegoś systemu opartego o poole na przykład.

4. Prawidłowe użycie operatora new


Ostatnia uwaga dotyczy programistów C++. Jest taka maniera wielu programistów, iż alokują pamięć za pomocą operatora new, a poprawność wykonania sprawdzają w ten sposób, że sprawdzają, czy wynik jest różny od NULL. Oczywiście jeśli kompilator jest dostatecznie stary, albo ma odpowiednio ustawione flagi kompilacji, to okaże się, że to nawet zadziała. Jednak według oficjalnego standardu mamy, że zostanie tu rzucony wyjątek.... No i może być inny problemik w tym momencie. Kod, który napisaliśmy dawno temu, ktoś użył w innym programie, gdzie flagi kompilacji muszą byc ustawione inaczej, a kompilator jest na tyle nowy, że działa wg standardów.
Tak więc pozostaje nam, albo zgodnie ze standardem łapać wyjątek, albo dać znać lokalnie, że w tym miejscu życzymy sobie, aby została zwrócona wartość NULL w razie niepowodzenia. Efekt ten uzyskujemy wywołując operator new z parametrem nothrow. A wygląda to następująco:
#include <new>
[...]
	x = new(std::nothrow) TMyType();
Jak państwo zauważyli, jeśli chcemy wywołać operator new z parametrem std::nothrow, musimy dołączyć plik new.

Na tym kończę i pozdrawiam
Tomasz Kaczanowski

2014-03-18 18:05:03


Alokatory zarządzanie pamięcią operator new nothrow malloc free programowanie