Używanie delete

Używanie delete
BL
  • Rejestracja:ponad 16 lat
  • Ostatnio:4 miesiące
0

Prawie każdy i w prawie każdej książce, powiedziane jest, że przed zakończeniem programu wszystkie zmienne dynamiczne należy usuwać poprzez operator delete (oczywiście zakładając, że tworzymy je operatorem new). Dziś znajomy powiedział mi, że po wyłączeniu programu, wszystkie zmienne, które były deklarowane na stercie, są automatycznie zwalniane przez system operacyjny i dzieje się tak już od Windowsa 98 włącznie. Czy w takim razie faktycznie zwalnianie pamięci poprzez delete jest koniecznie przed zamknięciem programu? I czy naprawdę tak się dzieje, że systemy operacyjne same zwalniają pamięć, która została zaniechana przez program?


Zapraszam osoby początkujące na indywidualne szkolenia z programowania w JAVA. Dla najlepszych, po zakończonym kursie, praca ze stawką początkową 10-12tyś netto. Kurs trwa od 6 do 24 miesięcy, 1-2h w tygodniu, 150PLN/h. szkolenia kropka java malpaa gmail kropka com
nav
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 2 lata
  • Lokalizacja:Warszawa
1

Zwalnianie nie jest konieczne, jednak sprzątanie po sobie jest dobrym nawykiem (jak napisałeś, sprzątnie to system po zamknięciu aplikacji). W szczególności w aplikacjach, które pracują ciągle.
Kolejne alokacje powodują, że program ma "używa" coraz więcej pamięci, której system nie może przeznaczyć na coś innego. W przypadku, gdy skończy się pamięć fizyczna, zaczyna się zrzucanie części pamięci na dysk (swapowanie) i późniejsze odczytywanie na żądanie, co ma znaczny wpływ na wydajność całego systemu.

Tak z grubsza.


utf-8 rlz! ٩(ಥ_ಥ)۶
edytowany 2x, ostatnio: nav
Xupicor
  • Rejestracja:ponad 16 lat
  • Ostatnio:ponad 8 lat
1

Jeśli pracujesz na systemie, który to zrobi (a niemal na pewno tak jest) to możesz zostawić zwolnienie pamięci systemowi - choć dobrą praktyką tego bym nie nazwał. Jak z każdą "troszkę szemraną" praktyką - ważne, żeby wiedzieć co się robi i kiedy może ona zawieść, a jeśli chcemy, żeby w ogóle nigdy nie zawodziła, to pewnie trzeba to zrobić inaczej. ;P
Może być też tak, że system zwolni pamięć za Ciebie trochę szybciej (bo jako jeden spory kawałek w jednym wywołaniu). Dla przykładu, programy w tutorialach FLTK wyglądają tak:

Kopiuj
// hello.cxx (example1)

#include <fltk/Window.h>
#include <fltk/Widget.h>
#include <fltk/run.h>
using namespace fltk;

int main(int argc, char **argv) {
  Window *window = new Window(300, 180);
  window->begin();
  Widget *box = new Widget(20, 40, 260, 100, "Hello, World!");
  box->box(UP_BOX);
  box->labelfont(HELVETICA_BOLD_ITALIC);
  box->labelsize(36);
  box->labeltype(SHADOW_LABEL);
  window->end();
  window->show(argc, argv);
  // a deleta, sory, dilejta, brak ;)
  return run();
}

Co oczywiście nie znaczy, że można sobie delete i delete[] w ogóle podarować. Nie chcesz przecież, żeby Twój program np co wywołanie funkcji foo() gubił pamięć, aż do wywalenia się z powodu braku pamięci? :P
edit: No chyba, że użyjemy sprytnych wskaźników, wtedy delete znacznie rzadziej zobaczymy. ;)


edytowany 2x, ostatnio: Xupicor
Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 6 godzin
2

i dzieje się tak już od Windowsa 98 włącznie
Windows 98 nie ma tu nic do rzeczy. Dzieje się tak i w DOS-ie. Od zawsze, nazwijmy to.

Ale dlaczego mimo to należy używać delete? Bo czasem alokujesz pamięć (new) w pętli. Czasem w inny powtarzalny sposób (np. pod kliknięciem przycisku).
Brak zwalniania pamięci wtedy spowoduje, że z czasem program będzie zajmował coraz więcej pamięci, aż do niebotycznych rozmiarów.
W małym programie, gdzie alokujesz sobie jakąś tablicę, coś tam wyświetlasz, zwalniasz i koniec – to nie ma znaczenia. Ale warto wyrabiać sobie dobre nawyki, a nie złe.

BL
  • Rejestracja:ponad 16 lat
  • Ostatnio:4 miesiące
0

Okej, dzięki za odpowiedzi.

Mam jeszcze jedno pytanie (tak mnie naszło przy kolacji :D) związane z pamięcią, pośrednio dynamiczną. Gdy alokujemy pamięć dla jakiejś struktury danych, to we wskaźniku trzymamy sobie jej adres pamięci. No i właśnie ... ten adres pamięci wskazuje na co? Na blok pamięci? Chodzi mi głównie o to, w jaki sposób system potem jest w stanie rozpoznać co kryje się pod danym adresem. Każdy adres, gdy go sobie wyświetlam, składa się z kilku znaków (jeśli dobrze pamiętam to 6) i jak się domyślam, raz może wskazywać na blok o wielkości 8 bitów, a raz 10 Mb. W jaki sposób to działa? Czy system wyszukuje blok pamięci o odpowiednim rozmiarze, któremu potem nadaje numer czy działa to jakoś inaczej? Przepraszam, jeśli napisałem to nie jasno, ale sam nie wiem za bardzo jak skonstruować stosowne pytanie.


Zapraszam osoby początkujące na indywidualne szkolenia z programowania w JAVA. Dla najlepszych, po zakończonym kursie, praca ze stawką początkową 10-12tyś netto. Kurs trwa od 6 do 24 miesięcy, 1-2h w tygodniu, 150PLN/h. szkolenia kropka java malpaa gmail kropka com
ZJ
Ogólnie glibc min. radzi sobie z tym tak, że przechowuje wielkość zaalokowanej pamięci w miejscu o jedno słowo przesunięte "do tyłu" od właściwego miejsca alokacji (dlatego niekiedy błędy o jeden mogą psuć zwalnianie pamięci).
MasterBLB
  • Rejestracja:około 19 lat
  • Ostatnio:8 dni
  • Lokalizacja:Warszawa
  • Postów:1454
0

Rozpoznaje to po typie tego wskaźnika-i tak na przykład definicja

Kopiuj
int *w=new int;

mówi kompilatorowi,że pod adresem przechowywanym we wskaźniku w siedzi dana typu int.To samo jeśli chodzi o struktury czy klasy.
Ciutkę innaczej ma się sprawa z tablicami,bo po stworzeniu trzeba samemu pamiętać ich rozmiar,nie ma jak go pobrać po stworzeniu.Nadto dobrze jest wskaźnik na początek tablicy definiować jako stały:

Kopiuj
int *const tab=new int[someNumber];

"Sugeruję wyobrazić sobie Słońce widziane z orbity Merkurego, a następnie dupę tej wielkości. W takiej właśnie dupie specjalista ma teksty o wspaniałej atmosferze, pracy pełnej wyzwań i tworzeniu innowacyjnych rozwiązań. Pracuje się po to, żeby zarabiać, a z resztą specjalista sobie poradzi we własnym zakresie, nawet jeśli firma mieści się w okopie na granicy obu Korei."
-somekind,
konkretny człowiek-konkretny przekaz :]
edytowany 4x, ostatnio: MasterBLB
0
MasterBLB napisał(a)

Rozpoznaje to po typie tego wskaźnika-i tak na przykład definicja

Kopiuj
int *w=new int;

mówi kompilatorowi,że pod adresem przechowywanym we wskaźniku w siedzi dana typu int.To samo jeśli chodzi o struktury czy klasy.

Bredzisz bracie jak potłuczony. new i delete w wersji nieprzeciążonej to tylko bezpieczniejsze nakładki na malloc i free. Czy free jest świadome istnienia typów? free nie pyta nawet o rozmiar bo rozmiar jest zawsze zapisywany przy dynamicznej alokacji, zazwyczaj w strukturze umieszczonej bezpośrednio przed zwróconym z alokatora adresem. Wiesz w ogóle jak wygląda operator delete? Prototypy ma następujące:

  1. operator delete(void*)
  2. operator delete(void*, const std::nothrow_t&)
  3. operator delete[](void*)
  4. operator delete[](void*, const std::nothrow_t&)

Nie ma ani konkretnego typu, ani też przekazywania rozmiaru, bo nikt normalny nie implementuje alokatorów tak żeby zwalnianie tego wymagało. Używałeś kiedykolwiek polimorfizmu? Obiekt klasy pochodnej jest trzymany przez wskaźnik na klasę bazową, wywołanie delete działa poprawnie chociaż rozmiar obiektów może być skrajnie różny (vie emulacja interface'ów poprzez klasy czysto wirtualne i "ciężkie" obiekty je implementujące).

Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 6 godzin
0

Gdy alokujemy pamięć dla jakiejś struktury danych, to we wskaźniku trzymamy sobie jej adres pamięci. No i właśnie ... ten adres pamięci wskazuje na co? Na blok pamięci?
tak. przy czym „blok” to dużo powiedziane. adres to liczba, kolejny numer bajtu w pamięci. nie ma w nim wcale napisane, czy to wskaźnik na inta, czy na dziesięć stringów. ta informacja jest znana w momencie kompilacji programu (dlatego kompilator nie przepuści kodu gdy typ wskaźnika się nie zgadza), ale wszelkie typy i ich nazwy są po tym etapie tracone. wykonywalny program operuje na adresach i bajtach pamięci.

Czy system wyszukuje blok pamięci o odpowiednim rozmiarze, któremu potem nadaje numer czy działa to jakoś inaczej?
przydzielanie pamięci jest dwuetapowe.
Pierwszy etap, to gdy w momencie uruchamiania programu system operacyjny przydziela programowi jakiś początkowy obszar pamięci, na przykład 2 megabajty. zapisuje sobie tę informację gdzieś w tablicy (mapie pamięci) że taki-a-taki program zajmuje tyle-a-tyle RAMu, i od tego-a-tego miejsca. Te informacje służą potem do wyszukiwania wolnych bloków dla innych programów, by się w pamięci nie nachodziły.

Systemu nie interesują takie drobnostki jak zmienne wewnątrz programu: operuje na całych programach.

Drugi poziom alokacji pamięci odbywa się wewnątrz programu. Zajmuje się tym menedżer pamięci będący częścią tzw. runtime, czyli biblioteki standardowej języka programowania, w którym program został napisany. Są to funkcje typu malloc/free, new/delete i inne, zależne od języka.
Istotne jest, że są one częścią naszego programu, a nie systemu operacyjnego. Dlatego działają w ramach przydzielonego przez system obszaru, operując na niewielkich blokach, o rozmiarach pojedynczych zmiennych lub niewiele większych. Jednak zasada działania jest podobna: alokacja pamięci polega na dokonaniu do jakiejś tablicy czy struktury wpisu, że pod danym adresem ileś-tam bajtów jest zajętych.
Algorytm przydzielania pamięci pilnuje, by zmienne dynamiczne nie nachodziły na siebie, oraz by pamięci nie marnować (np. małe zmienne alokować obok siebie, bo jak będą poszatkowane to zabraknie ciągłego miejsca dla dużych).
Są różne algorytmy przydzielania pamięci.
Zwolnienie pamięci polega na usunięciu wpisu z tablicy – w zasadzie nic poza tym. Zmienna nie jest wymazywana, unicestwiana - po prostu blok jest oznaczany jako wolny do przydzielenia innej zmiennej.

Z poziomu języka nie mamy jednak dostępu do wewnętrznych struktur menedżera pamięci - a nawet jeśli się do tego dobierzemy, to nie ma to wielkiego sensu, gdyż format tych struktur może się zmieniać z wersji na wersję kompilatora.

_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
0
Azarien napisał(a)

Zwolnienie pamięci polega na usunięciu wpisu z tablicy – w zasadzie nic poza tym. Zmienna nie jest wymazywana, unicestwiana - po prostu blok jest oznaczany jako wolny do przydzielenia innej zmiennej.
No nie zupełnie, przy zwolnieniu sprawdza się czy poprzedni blok pamięci jest wolny, jeżeli tak to łączy ich w jeden większy, po czym (a może przed tym) sprawdza czy poprzedni blok pamięci jest wolny, jeżeli tak to znowu łączy ich w jeden większy.


Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
msm
Administrator
  • Rejestracja:prawie 16 lat
  • Ostatnio:4 miesiące
0

Z poziomu języka nie mamy jednak dostępu do wewnętrznych struktur menedżera pamięci - a nawet jeśli się do tego dobierzemy, to nie ma to wielkiego sensu, gdyż format tych struktur może się zmieniać z wersji na wersję kompilatora.

A nie przypadkiem systemu operacyjnego? Wydaje mi się że malloc (na systemach Windows) to tylko wrapper na VirtualAlloc czy inny AllocateHeap.

_13th_Dragon
Tak tylko ci się wydaje, owszem w końcu jest wywoływany Alloc systemowy, ale przydziela się Dużymi paczkami jak to w uproszczeniu opisał Azarien.
msm
Z ciekawości sprawdziłem - okazuje się że rację mieliśmy... jednocześnie. Malloc to prawie czysty wrapper na HeapAlloc (nie analizowałem linijka po linijce, ale jest tam wywołanie HeapAlloc, kilka chceków i prawie żadnej dodatkowej logiki - za mało). Za to new (czyli de facto normalna alokacja) to nie tylko prosty wrapper na malloc bo ma bardzo mocno złożone drzewko wywołań funkcji więc całkiem prawdopodobne że robi to tak jak napisałeś (Peace).
_13th_Dragon
Jak analizujesz kod wygenerowany przez jeden z kompilatorów to masz bardzo ograniczoną wiedzę na ten temat (do jednego kompilatora). Tak jak mówiłem malloc przydziela pamięć z wcześniej zaalokowanej pamięci lub jeżeli jej brak to przydziela kolejny duży fragment przez systemowego Alloca (potem go kroi), nie potrzebna na to żadna skomplikowana logika. Co do new to ma on kilka dodatkowych zadań do wykonania, więc nie ma co się dziwić że jest bardziej skomplikowany niż malloc.
msm
Analizowałem msvcrt (malloc) i kod wygenerowany przez gcc (new). Pewnie masz ofc rację ale przyjrzę się temu bo wygląda ciekawie.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)