skąd delete zna rozmiar

0

Cześć :)
Weźmy pod lupę operator delete. Jako parametr przyjmuje on wskaźnik, a zatem adres obiektu. Pytanie brzmi, skąd wie ile należy zwolnić pamięci? Bo wskaźnik pokazuje na adres, pod którym jest zaalokowany obiekt, ale wskazuje na POCZĄTEK miejsca alokacji.

0

sizeof(obiekt) ;)

0

O ile pamiętam to jest zależne od implementacji, standard tutaj nic nie narzuca; natomiast większość kompilatorów (wszystkie?) zapisują rozmiar obiektu w osobnym polu (niejawnie).

0

Dobrze, ale co teraz?
using namespace std;

struct A{
};
int main()
{
  A *a = new A;
  void *b = &(*a);
    delete b;
    return 0;
}

Co prawda dostaję warning'a, ale wg valgrinda wszystko jest zwolnione więc jakoś się orientuje. A przecież teraz delete nie wie jaki jest typ wskaźnika.

1

delete jest najczęściej implementowane jako zwykłe wywołanie free(ptr). I tak jak wspomniał już wcześniej @Patryk27 typowe implementacje malloc() zapisują sobie podczas alokacji rozmiar danego obiektu, który jest później odczytywany przez free().

0

czyli mam rozumieć, że malloc w swojej wewnętrznej implementacji ma gdzieś pewnie jakąś mapę, i mapuje wskaźnik ( adres obiektu) na jego rozmiar?

0

Nah', to byłoby mało wydajne.
Być może zapisuje rozmiar wskaźnika np.cztery/osiem bajtów (x86/x86-64) przed rzeczywistym adresem/wskaźnikiem w stylu:

void* malloc(size_t size)
{
 void* addr = allocate_memory(size + sizeof(size_t)); // to nie jest ofc. rzeczywista nazwa funkcji, tylko dla zobrazowania
 
 *(size_t*) addr = size;

 return addr+sizeof(size_t);
}

Ale jak powiedziałem: implementation-defined. Może być jak powyżej, może też tak jak pisze @Krolik, albo na co tylko jeszcze wpadną twórcy kompilatora.

2

natomiast większość kompilatorów (wszystkie?) zapisują rozmiar obiektu w osobnym polu (niejawnie).

Daj jakiś link do tych rewelacji, bo inaczej mam wrażenie, że to ukryte pole Ci się przyśniło.

Kompilator nie musi zapisywać żadnego pola, ponieważ kompilator zna rozmiary obiektów, z którymi ma do czynienia - przecież kompilator ma dostęp do pełnej definicji obiektu, więc zna nie tylko rozmiar ale i całą strukturę. A jak niby kompilator generuje kod alokujący pamięć pod nowy obiekt? Przecież nie ma jeszcze obiektu i nie ma żadnego pola z rozmiarem, a malloc potrzebuje rozmiaru...

Abstrahując od tego, delete w C++ deleguje zwalnianie pamięci do free, a free nie bierze rozmiaru obiektu. Wystarczy wskaźnik. Więc kompilator nawet nie musi znać rozmiaru aby zwolnić obiekt (z drobnym wyjątkiem w przypadku tablic - ale tam chodzi o wołanie destruktorów - stąd rozróżnienie między delete a delete[]).

To alokator pamięci dynamicznej śledzi, ile miejsca zajmuje dany blok pamięci. Zwykle ta wartość jest większa niż rozmiar obiektu - w przypadku wielu alokatorów, zaalokowanie np. obiektu o rozmiarze 1025 B może spowodować zajęcie 2048 B pamięci; czyli niemal 2x więcej (reszta się marnuje!). Rozmiar bloku może, ale nie musi być zapisany - wszystko zależy od implementacji alokatora. Alokator może np. alokować obiekty różnych rozmiarów z różnych, dedykowanych pul i wtedy rozpoznawać rozmiar obiektu po adresie. Albo może trzymać sobie rozmiary tuż przed blokiem. Albo zamiast dokładnego rozmiaru tylko klasę rozmiaru. Możliwości jest wiele.

czyli mam rozumieć, że malloc w swojej wewnętrznej implementacji ma gdzieś pewnie jakąś mapę, i mapuje wskaźnik

To nie jest takie bez sensu jak się wydaje. Alokator ma kontrolę nad tym jakie wskaźniki daje aplikacji. Może nie mieć mapy wskaźników, ale może mieć mapę stron pamięci i np. robić założenie, że dana strona zawiera obiekty tylko o jednym określonym rozmiarze, co jest wskazane też z innych powodów (fragmentacja!). Wtedy wystarczy trzymać rozmiar jeden raz, dla całej strony, co zajmuje dużo mniej miejsca niż dla każdego bloku osobno. Poza tym malloc/free jest i tak o wiele powolniejsze niż wyszukiwanie w strukturze typu hashmap, więc raczej nie byłoby to zauważalne.

0

Daj jakiś link do tych rewelacji, bo inaczej mam wrażenie, że to ukryte pole Ci się przyśniło.

Fakt, najwyraźniej coś mi się, ehm, pomyliło - aż zdesasemblowałem sobie kilka przykładów dla potwierdzenia, że tak się nie dzieje; mój błąd.

0
Krolik napisał(a):

natomiast większość kompilatorów (wszystkie?) zapisują rozmiar obiektu w osobnym polu (niejawnie).

Daj jakiś link do tych rewelacji, bo inaczej mam wrażenie, że to ukryte pole Ci się przyśniło.
Oczywiście kompilator nie zajmuje się dynamicznym przydzielaniem pamięci, tylko libc, ale takie pole faktycznie istnieje (oczywiście nie zawsze, jest to ściśle zależne od implementacji). Dodatkowo nie przechowuje rozmiaru obiektu tylko bloku. Oto fragment ze źródeł newlib:

    An allocated chunk looks like this:  


    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of previous chunk, if allocated            | |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of chunk, in bytes                         |P|
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             User data starts here...                          .
            .                                                               .
            .             (malloc_usable_space() bytes)                     .
            .                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of chunk                                     |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


    Where "chunk" is the front of the chunk for the purpose of most of
    the malloc code, but "mem" is the pointer that is returned to the
    user.  "Nextchunk" is the beginning of the next contiguous chunk.

Najwyraźniej również GNU libc implementuje to w podobny sposób: http://ideone.com/cqhjxm

Ale tak jak już @Krolik wspomniał, możliwości jest wiele.

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