Przeładowany operator new i delete

0

Witam

Piszę sobie (w ramach ćwiczeń) programik, w którym jest przeładowany operator new.
Operator ten przy tworzeniu pierwszego obiektu tej klasy rezerwuje od razu pamięć na 100 takich obiektów:

void * Obiekt::operator new(size_t rozmiar)
{
	// inne operacje

	poczatek = new char[rozmiar * 100];

	// inne operacje

	return poczatek;

	// inne operacje
}

poczatek jest statycznym wskaźnikiem do void i cały czas wskazuje na początek tej stu elementowej tablicy.

Przy tworzeniu kolejnych obiektów tej klasy operator ten zwraca po prostu adres z tego wcześniej zarezerwowanego obszaru.

Mam też przeładowany operator delete, który przy skasowaniu ostatniego obiektu tej klasy powinien zwrócić zarezerwowaną pamięć na te 100 obiektów. No i tu moje pytanie:
Jak mam zwolnić tę pamięć:

1:if(ile_obiektow == 0)
{
delete poczatek;
}


2:<code>if(ile_obiektow == 0)
{
	delete [] poczatek;
}

3:if(ile_obiektow == 0)
{
void * usuwany = poczatek;

for(int i = 0; i < 100; i++)
{
	delete usuwany;

	Obiekt * tmp = reinterpret_cast<Obiekt *>(usuwany);
	tmp++;

	usuwany = tmp;
}

}


Wydaje mi się, że opcja 3 jest na pewno poprawna, ale nie wiem czy opcja 2 nie zrobi tego samego szybciej ;)
Która opcja jest poprawna i dlaczego?
0

Wersje 1 i 2 obie poprawne.
Z tym że jeżeli początek jest statyczną składową klasy Obiekt to nie masz gwarancji że ona zainicjalizuje się zanim nadejdzie pierwsze wywołanie operatora new. Oczywiście w operatorze new masz też sprawdzać:
if(!ile_obiektow) poczatek = new char[rozmiar * 100];
Zaś zwracać masz nie zawsze początek, tylko że pewne jego przesunięcie.

0

Oczywiście dokonuje tego sprawdzenia i zwracam przesunięcie względem początku, ale nie pisałem tego, żeby nie zaciemnić kodu i napisałem tylko: // inne operacje ;)
Do zmiennej początek wpisuje adres dopiero przy pierwszym wywołaniu operatora new więc chyba nie ma dla mnie znaczenia czy zdąży się ona zainicjalizować adresem zerowym.

A co do samego usuwania to obie wersje (1 i 2) zwolnią całą zarezerwowaną pamięć? Niczym się nie różnią?

0

Ale sprawdzasz zmienną ile_obiektow która pewnie też jest statyczna, więc i tak możesz mieć problem.

Obie wersje zrobią to samo dopóki zwalniana tablica jest jakimś typem beż destruktora.
delete ptr; // wywoła destruktor jednego obiektu
delete[] ptr; // wywoła destruktory dla wszystkich obiektów zwalnianej tablicy.
ponieważ char i tak NIE MA destruktora to wszystko jedno co zastosować.

0

Hmm.. Z tego co pamiętam z Symfonii to:

int *wsk = new int;
delete wsk;

int *wsk_tab = new int[10];
delete [] wsk;

int destruktora nie ma, a używa się wersji delete []
Ale mogę się mylić :)

A jeszcze co do wersji delete [] ptr, to skąd kompilator wie ilu elementowa jest tablica i ile destruktorów ma uruchomić? Np. jeżeli jesteśmy w innym zakresie ważności i nie widzimy definicji tej tablicy - mamy tylko do niej wskaźnik.

0

@nowy zasadniczo to dobra praktyka żeby tablice zwalniać delete[] a pojedyncze obiekty przez delete, ale to raczej na wypadek gdybyś nagle postanowił zmienić typ jakiejś zmiennej na jakąś klasę (bo akurat wystąpiła jakaś potrzeba) i wtedy nie masz dodatkowej roboty z dodawaniem []. Ale faktem jest że w przypadku akurat typów wbudowanych nie ma róznicy.

0

Człowiek całe życie się uczy ;)
A moglibyście mi jeszcze powiedzieć skąd kompilator będzie wiedział ile tych destruktorów uruchomić mając sam tylko wskaźnik do tablicy?

Np:Klasa * wsk = new Klasa[100];

a w jakiejś funkcji mamy tylko ten wskaźnik wsk:

delete [] wsk;

I skąd kompilator wie obiektów jest w tablicy?

Dzięki za zainteresowanie :)
0

W sumie jeżeli chodzi o typy wbudowane to też nie wiem skąd kompilator wie ile elementów ma tablica i jak to zwalnia:

Np mamy tablicę:int *wsk = new int[100];
A gdzieś w jakiejś funkcji tylko wskaźnik do niej:
delete []wsk; // lub delete wskTo jak kompilator to załatwia?

0

System pamięta ile zarezerwował. Można to nawet od niego wyciągnąć :D

0
#include <iostream>
using namespace std;

class Foo
  {
   double x,y;
   public:
   Foo():x(0),y(0)
     {
      cout<<"Konstruktor"<<endl;
     }

   ~Foo()
     {
      cout<<"Destruktor"<<endl;
     }

   void * operator new(size_t sz)
     {
      cout<<"new "<<sz<<endl;
      return ::new char[sz];
     }
   
   void operator delete(void *ptr)
     {
      cout<<"delete "<<endl;
      delete[] (char*)ptr;
     }

   void * operator new[](size_t sz)
     {
      cout<<"new[] "<<sz<<endl;
      return ::new char[sz];
     }

   void operator delete[](void *ptr)
     {
      cout<<"delete[] "<<endl;
      delete[] (char*)ptr;
     }

  };

int main()
  {
   Foo *F=new Foo;
   cout<<"----"<<endl;
   Foo *tn=new Foo[10];
   cout<<"----"<<endl;
   delete[] tn;
   cout<<"----"<<endl;
   delete F;

   cin.sync();
   cin.get();
   return 0;
  }

Nie wiem czy każdy kompilator robi to w ten sam sposób, to akurat może zależeć od implementacji.
Jedynie proszę zauważyć że (w przypadku gcc) od operatora new[] prosi 8 bajtów pamięci więcej niż trzeba.
Zaś jeżeli zmienimy double na char lub short to prosi tylko o 4 bajty więcej.

0

A moglibyście mi jeszcze powiedzieć skąd kompilator będzie wiedział ile tych destruktorów uruchomić mając sam tylko wskaźnik do tablicy?

Kompilator nie wie. Zarządzenie pamięcią to sprawa systemu i to on się tym zajmuje - kompilator tylko 'prosi' o zwolnienie adresu.

Ładny przykład praktyczniego działania podał @up.

0
MSM napisał(a)

Kompilator nie wie.
Niestety musi wiedzieć bo system daje pamięć blokami po co najmniej 4 bajty.
Zaś jeżeli zrobimy klasę rozmiarem 1 bajt:

class X { public: char x; X():x(x) {} ~X() { cout<<"x"<<endl; } };

i przedzielimy pamięć:

X *tb=new X[2];

to zwolnienie pamięci:

delete[] tb;

wywoła dokładnie dwa destruktory.
Więc nie może polegać na systemie, musi gdzieś zanotować ile sztuk tych X zostało przydzielono aby wywołać dokładnie tyle destruktorów.

0

a patrzyłeś kiedyś jak działa malloc/free? w funkcji free nie podajesz ile bajtów chcesz zwolnić, tylko wskaźnik do adresu który zwalniasz, a system zwalnia od razu cały blok. nie jestem pewien, ale wydaje mi się że operatory new i delete wywołują te (lub podobne) funkcje

0

A patrzyłeś kiedyś jak działa new[] i delete[] ?
W poście na poprzedniej stronie umieściłem przykładowy kod, operator new[] dla 10 obiektów klasy o rozmiarze 16 bajtów przydziela 168 bajtów pamięci.

1

Napisałem przed chwilą na ten temat esej na 300 linijek, ale doszedłem do wniosku że nie będę tego wrzucał na forum bo nic wybitnie odkrywczego nie znalazłem a treść to w 50% listingi kodu asemblerowego. Z drugiej strony, szkoda mojej godziny ponad czasu. Pisałem to pierwotnie jako post, więc formatowanie Coyote. Jeśli kogoś interesuje, wrzuciłem na pastebina -

0

No ładnie ładnie :)
Dzięki wszystkim za odpowiedzi, teraz jest już to dla mnie jaśniejsze.

Pozdrawiam

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