Konkurs #3: automatyczne zwalnianie pamięci

6

Tym razem zabawa w małe GC w Pascalu;

Należy tak zmodyfikować ten kod:

// miejsce na Twój kod

Procedure MyProc;
Var Data: PByte;
    I   : uint16;
    // miejsce na Twój kod
Begin
 // miejce na Twój kod

 Data := AllocMem(10);

 For I := 0 To 9 Do
  Data[I] := Random(High(Byte));

 For I := 0 To 9 Do
  Write(Data[I], ' ');

 Writeln;
End;

Begin
 MyProc;

 Writeln('-- end --');
 Readln;
End. 

Aby przy wyjściu z funkcji MyProc, pamięć zaalokowana przy pomocy AllocMem była automatycznie zwalniana.

Nie można modyfikować już napisanego kodu (poza usunięciem tych komentarzy :P), a swój kod można wstawiać jedynie w miejsca oznaczone tymi trzema komentarzami (czyli przed procedurą MyProc, w jej lokalnych zmiennych oraz na samym jej początku).

Kompilator: Delphi lub FPC (można używać dowolnych przełączników), kod nie musi być przenośny między platformami/systemami, nie może również zawierać żadnych wycieków pamięci.

Czas: od dzisiaj przez tydzień, czyli do południa 7 grudnia, rozwiązania proszę przesyłać mi na PW ;)
Nagroda: +3.5 punktu do szacunku ;]

Powodzenia! :D

0

Mam pytanie.
Czy trzeba faktycznie zrobić coś w stylu GC czy wystarczy po prostu zwolnić Data?

0

Ten "odśmiecacz" powinien działać dla nieokreślonej liczby zmiennych w dowolnych procedurach/funkcjach, więc zwalnianie jedynie zmiennej "Data" nie wchodzi jako-tako w grę.

1

Ehh, a chciałem zrobić taki cheat :P

Procedure MyProc;
Var Data: PByte;
    I   : uint16;
    // miejsce na Twój kod
    x: TBytes;
    info: Pointer;
Begin
 // miejce na Twój kod
  info := TypeInfo(TBytes);
 asm
     mov eax, dword [@exit+1]   // pobranie rozmiaru tablicy z linijki:
                                           // Data := AllocMem(10);
     // przygotowanie parametrów dla funkcji DynArraySetLength
     push eax                   // odłożenie na stos rozmiaru tablicy
     mov eax, esp             // pobranie wskaźnika na rozmiar tablicy
     push eax                   // odłożenie na stos wskaźnika na rozmiar tablicy
     lea eax, x                  // eax = adres tablicy
     mov ecx, 1                // ecx = liczba wymiarów tablicy
     mov edx, info;            // edx = wskaźnik na typ tablicy
     call DynArraySetLength

     mov eax, x            // Data := @x[0];
     mov Data, eax       // Data := @x[0];
     pop eax                // czyszczenie stosu - usuniecie rozmiaru tablicy
     lea eax, dword [@exit+13]  // ominiecie wszystkich instrukcji
     jmp eax                           // związanych z Data := AllocMem(10);
     @exit:
 end;
 Data := AllocMem(10);

 For I := 0 To 9 Do
  Data[I] := Random(High(Byte));

 For I := 0 To 9 Do
  Write(Data[I], ' ');

 Writeln;
End;
3

Rozwiązanie typu GOOP (= Good Old Objective Pascal)

unit MojAlokatorUnit;

class MojAlokator
public
  class function AllocMem(Size: PtrUInt): pointer; // uruchom globalny AllocMem i dodaj do fListaBlokow
  class procedure FreeAll(); 
private
  fListaBlokow: cos-tam;
end;

function AllocMem(Size: PtrUInt): pointer; // wywołuje MojAlokator.AllocMem lub robi sama całą robotę na globalnej zmiennej (fuj)

finalization
  MojAlokator.FreeAll();
end.
3

Aby przy wyjściu z funkcji MyProc, pamięć zaalokowana przy pomocy AllocMem była automatycznie zwalniana.

Procedure MyProc;
Var Data: PByte;
    I   : uint16;
    MemGuard: IMemGuard;
Begin
    MemGuard := MojAlokator.NewGuard;

...

unit MojAlokatorUnit;
type
  TMemGuard: class(TInterfacedObject, IMemGuard)
  public
 // wywolane z MojAlokator.NewGuard z parametrem = fListaBlokow.Count
     constructor Create(BlockCnt: integer); virtual;
// wywolaj MojAlokator.FreeUntil(fBlockCnt) - zwalnia wszystkie bloki powyżej wskazanej bariery - zaczynając od fListaBlokow.Count - 1
     destructor Destroy; override; 
  private
     fBlockCnt: integer;   
  end;
2

Przyszła pora na rozstrzygnięcie konkursu:
Łącznie zostały przedstawione trzy rozwiązania, jedno przez @lukasz1235 , drugie @vpiotr oraz trzecie przez @Tajiri (edit: właściwie to w innej kolejności chronologicznej, ale nie chce mi się przeformatowywać postu ;p).

Łukaszowe rozwiązanie (http://ideone.com/JxXGKK dla x86 oraz http://pastebin.com/wzh0UArC dla x86-64) opiera się, z tego co widzę, na nadpisywaniu adresu powrotu, przez co po opuszczeniu funkcji, sterowanie kodu przechodzi do procedury exitHandler, która zwalnia bloki, a następnie skacze pod właściwy adres.

Rozwiązanie zaproponowane przez Piotra oraz Tajiri jest tym, na które również wpadłem i ja wpadłem podczas pisania treści tego konkursu, a polega na tym założeniu, że interfejsy są zarządzane specjalnie przez kompilator (refcountowane; podobnie sprawa ma się do stringów oraz dynamicznych tablic).

Tajiri łącznie podesłał dwa rozwiązania; jedno, które możecie zobaczyć w jego poście powyżej, a które nie pasuje w 100% do założeń konkursu, oraz drugie, którego zasada działania jest właściwie identyczna do kodu Piotra, więc nie wymaga specjalnego omówienia - http://www.speedyshare.com/5dBy8/rozwiazanieXE4.zip

A mój kod korzysta dodatkowo z makr Free Pascala - http://ideone.com/rtiqP6 ;)

Więc +3.5 punktu szacunku dla Łukasza, Piotra oraz Tajiri i dzięki za udział! ;)

0

@Patryk27: Zarówno do mojego jak i Twojego rozwiązania są przypadki patologiczne.

Do Twojego (zagnieżdżenie):
http://ideone.com/vF9zy3

Do mojego - musiałyby być takie alokacje:

  • alokacja A
  • alokacja B
  • zwolnienie A
  • zwolnienie B

1 użytkowników online, w tym zalogowanych: 0, gości: 1