Cześć i witaj. Teraz, gdy rozumiemy dynamiczną alokację pamięci,
musimy to zaimplementować, musimy właściwie wiedzieć, jak to zrobić w kodzie c.
Więc porozmawiamy o trzech naprawdę ważnych funkcjach,
malloc, calloc, realloc.
Widzisz, że mają trochę nazwy, w których alokacja
pamięci jest calloc i realokacja.
Najprostszym i najpowszechniejszym sposobem alokacji pamięci jest użycie
standardowej funkcji bibliotecznej o nazwie malloc.
To alokacja pamięci dynamicznej w czasie wykonywania.
Aby wywołać tę funkcję, musisz dołączyć standardowy plik nagłówkowy lib.
Jako argument określasz liczbę bajtów pamięci, którą chcesz alokować.
Ta funkcja zwraca adres pierwszego bajtu przydzielonej pamięci.
Ponieważ adres jest zwracany, wskaźnik jest jedynym miejscem, w którym można go umieścić.
Dlatego właśnie dynamiczna alokacja pamięci jest możliwa tylko dzięki
tej koncepcji wskaźnika, ponieważ zwraca on adresy.
Możesz umieścić adres tylko we wskaźniku, to jest to, co przechowuje wskaźnik, wskaźnik przechowuje adres.
Oto jak to zrobić, bardzo, prosta składnia.
Zwykle podczas tworzenia wskaźnika nie musisz tego robić.
Możesz stworzyć swój wskaźnik i ustawić go na null.
A potem w następnej linii możesz malloc.
Ale tutaj jest standardowa składnia, którą tworzysz swój wskaźnik int,
wskaźnik do typu int.
A potem zauważysz, że dzwonisz do malloc.
I tutaj, mówimy malloc, mamy zamiar utworzyć 100 bajtów dla malloc,
a następnie przerzucić to na wskaźnik int.
Ponieważ malloc zwraca tylko adres, musisz rzutować go
na wskaźnik końcowy, ponieważ ten adres
przechowuje wskaźnik int.
To jest standardowy sposób.
Teraz żądasz 100 bajtów pamięci i
przypisujesz ją do adresu bloku pamięci do numeru p.
pNumber wskaże pierwszą lokalizację int na początku z przydzielonych 100 bajtów.
Oznacza to, że ponieważ liczby całkowite mają 4 bajty na sztukę, może
zawierać 25 wartości int.
I znowu przy założeniu, że masz 4 bajty.
Teraz, w zależności od używanego systemu, mogą być większe, może mniejsze.
Więc powinieneś zobaczyć z tym problem. Ten kod nie
będzie działał w różnych systemach, ponieważ alokujesz na podstawie założenia
opartego na założeniu rozmiaru int.
Więc nie jest dobrze używać malloc w
ten sposób ze stałą liczbą 100.
Chcesz użyć operatora sizeof.
Dlatego lepiej byłoby usunąć założenie, że ints to 4 bajty.
Zamiast tego, jeśli chcesz przydzielić miejsce na 25 liczb całkowitych wewnątrz argumentu malloc,
określ liczbę liczb całkowitych,
które chcesz przydzielić, a następnie pomnóż ją przez rozmiar tych liczb.
Dzięki temu kod będzie znacznie bardziej przenośny.
Ten kod będzie teraz działał na różnych systemach, różnych systemach operacyjnych, różnych architekturach.
Nadal rzutujesz wartość zwracaną na adres, ponieważ musi on wskazywać na typ
int, więc mówisz int star. Jedyna różnica polega na tym, że używasz operatora sizeof.
Operator Sizeof jest naprawdę ważny, gdy alokujesz pamięć za pomocą malloc lub
używając calloc lub realloc.
Zawsze używasz i chcesz używać operatora
sizeof dla typu, który przydzielasz, a następnie mnożymy go
przez liczbę żądanych liczb, więc jeśli chcesz przechowywać
25 int, więc robisz 25.
Tak więc powyższy argument malloc wyraźnie wskazuje, że należy udostępnić wystarczającą liczbę
bajtów na umieszczenie 25 wartości typu int.
Ponadto, jak wspomniałem, zwróć uwagę na obsadę. To konwertuje adres, zwracany
przez funkcję na wskaźnik typu na int i int wskaźnik.
Malloc zwraca wskaźnik typu void, dlatego jest tak przydatny.
Zwraca ten adres, ale jest to wskaźnik pustki.
Pamiętaj, rozmawialiśmy o wskaźnikach void i dlaczego wskaźniki void są
przydatne, ponieważ teraz możesz mieć funkcję, która może przyjmować różne parametry i może
zwracać wskaźnik void. Teraz znowu widzisz moc
zwrócenia wskaźnika pustki, ale aby
go użyć, aby faktycznie przypisać do niego dane, aby usunąć odniesienie, musisz
go rzucić.
Gdybyśmy nie chcieli tego rzucić, moglibyśmy powiedzieć void star liczba p.
Nie przesyłamy tego, ale wtedy musielibyśmy w końcu rzucić
to, gdy chcieliśmy go usunąć.
Ale właśnie dlatego musimy rzucać z malloc. Zwraca adres i chcesz, aby wskazywał na
typ int, a zwraca typ void.
Możesz zażądać dowolnej liczby bajtów.
Zależy to jednak od systemu i ilości pamięci w systemie.
Więc jeśli spróbujesz utworzyć lub zażądać tony bajtów lub wystąpią pewne
ograniczenia, może się to nie powieść.
A gdy się nie powiedzie, zwraca wartość null, malloc zwróci wartość null.
I to jest dobre, ponieważ wskaźniki, gdy wskaźniki wskazują na zero, oznacza to, że
nie wskazują na żadną pamięć, więc
nie możesz przypadkowo uszkodzić ani zmienić żadnej pamięci.
Dlatego zawsze dobrze jest po wykonaniu malloc natychmiast
sprawdzić wartość null, zanim go użyjesz, tak jak to
jest podczas przypisywania danych lub
po prostu tak, jak to jest, gdy usuwasz odniesienie do wskaźnika.
Chcesz się upewnić, że faktycznie coś wskazujesz.
Więc tutaj przydzielamy trochę pamięci dla numeru p naszego wskaźnika, a
następnie mówimy, jeśli nie numer p.
Pamiętaj, że null to zero.
Możesz więc po prostu użyć na nim nie. Nie musisz faktycznie porównywać go z wartością null.
Możesz, możesz porównać go z wartością null, dzięki czemu będzie bardziej czytelny, może bardziej wyraźny.
Ale możesz po prostu powiedzieć nie liczba p, a to tak naprawdę mówi nie
jeden, a null to zero. Więc to sprawdzi, czy nie ma wartości null.
A jeśli jest zerowa, oznacza to w zasadzie, że
nie można było zaalokować pamięci. Jeśli nie jesteś w stanie przydzielić pamięci w swoim programie, masz kłopoty.
Więc to, co zwykle powinieneś zrobić w tym kodzie, aby poradzić
sobie z błędem alokacji pamięci, to przerwać, powinieneś
wywołać exit, powinieneś wyjść z programu. Ponieważ jeśli twój program nie może przydzielić pamięci, twój program jest zawiązany.
Więc zwykle jest to przerwanie wypadku, jeśli nie możesz przydzielić pamięci.
Przynajmniej kiedy to zrobisz, możesz wyświetlić komunikat
kończący program. To znacznie lepsze
niż zezwolenie na kontynuowanie programu, gdy nie byłeś w stanie przydzielić pamięci.
Ponieważ wtedy możesz kontynuować, możesz spróbować użyć tej pamięci,
będzie ona zerowa, więc program i tak ulegnie awarii,
ale niekoniecznie będziesz wiedzieć, dlaczego.
Musisz spojrzeć na stos wywołań. Debugowanie tego błędu zajmie dużo więcej czasu.
Uwalnianie pamięci. Teraz wspomniałem, że
ponieważ jesteś programistą odpowiedzialnym za alokację pamięci, musisz pozbyć się
pamięci, gdy już jej nie używasz, ponieważ pamięć jest istotnym
zasobem, szczególnie w systemach wbudowanych.
Nie powinieneś używać pamięci, jeśli jej nie potrzebujesz. Więc musisz uwolnić pamięć.
Zawsze chcesz zwolnić pamięć, gdy nie jest już potrzebna.
Pamięć przydzielona na stercie zostanie automatycznie zwolniona, gdy program będzie istniał, co jest
dobre, ponieważ wykonuje pewne czyszczenie.
Ale znacznie lepiej jest wyraźnie zwolnić pamięć, gdy skończysz z nią,
nawet jeśli jest to tuż przed wyjściem.
Jest czystszy,
zawsze rób to za darmo lub zawsze zwalniaj pamięć po
jej przydzieleniu, chcesz przestrzegać tej zasady. To surowa, ścisła zasada.
Zawsze zwalniaj pamięć, jeśli ją przydzielasz.
Wyciek pamięci występuje, gdy przydzielasz część pamięci dynamicznie i
nie zachowujesz odwołania, więc nie możesz go zwolnić.
Może nie masz możliwości odniesienia się do tego odniesienia, którego nie możesz. Nie wypuszczasz tego.
Nazywa się to wyciekiem pamięci. Jest przydzielona pamięć, ale nie jest już używana.
Dzieje się tak często, gdy jesteś w pętli i alokujesz pamięć w pętli.
Jesteś na bieżąco. Przydzielasz tę pamięć. Używasz go wewnątrz pętli,
a następnie wyskakujesz z pętli i już jej nie puszczasz.
Każda iteracja pętli ją przydziela.
Zatem aby zwolnić pamięć przydzieloną dynamicznie, nadal musisz
mieć dostęp do tego adresu.
Więc zwykle potrzebujesz wolnej pamięci, gdy skończysz jej używać i
zwykle w metodzie, którą ją przydzieliłeś.
Jednakże, jeśli przekazujesz tę pamięć do innej funkcji, a wtedy jest
to odpowiedzialność za tę funkcję, ale zwykle chcesz zwolnić pamięć i
funkcję, którą ją przydzielasz.
Aby zwolnić pamięć dla bloku dynamicznie
przydzielanej pamięci, którego adres zapisałeś we wskaźniku, wystarczy
wywołać funkcję free.
Jest to również w standardowej bibliotece. Po prostu przekazujesz wskaźnik do wolnej funkcji.
Zwykle po przekazaniu tego wskaźnika chcesz ustawić go na null,
ponieważ oznacza to, że nie masz przydzielonej pamięci.
Funkcja wolna ma formalny parametr typu void star,
więc możesz przekazać dowolny typ wskaźnika
i zadziała.
Nie musisz tego rzucać ani nic takiego.
Dopóki numer p zawiera adres, który został zwrócony podczas przydzielania pamięci, cały blok
pamięci będzie wolny do dalszego wykorzystania. Inne programy mogą korzystać z tej pamięci, twój
program może teraz korzystać z tej
pamięci, co ma znaczenie, gdy jesteś w systemie osadzonym.
A potem, jak wspomniałem, zawsze chcesz ustawić wskaźnik na
null, aby zaznaczyć, że ten wskaźnik już nie wskazuje na nic.
Ta pamięć została uwolniona.
Jest jeszcze jedna fajna funkcja o nazwie calloc.
Ma to kilka zalet w porównaniu z mallocem. Jest bardzo podobny
do malloc, ale przydziela pamięć jako liczbę elementów w danym
rozmiarze, a także inicjalizuje pamięć.
To jest różnica, inicjalizacja. Mówiłem o znaczeniu inicjalizacji.
Dlatego zawsze
chcesz zainicjować dane, aby zrozumieć, że są tam
dane, których się spodziewasz.
Kiedy usuwasz inicjalizację danych, mogą tam znajdować się dane, o których nie masz pojęcia.
Dlatego zawsze inicjuj swoje dane czymś.
Calloc jest trochę lepszy, ponieważ robi to za Ciebie.
Pobiera dwa argumenty, ma dwa parametry.
Liczba pozycji danych, dla których wymagana jest przestrzeń, rozmiar każdego elementu danych.
Jest zadeklarowany w standardowym nagłówku lib.
Nazywasz to tak, jak nazywasz malloc. Wygląda prawie tak samo.
W tym przykładzie tworzymy wskaźnik
całkowity i przydzielamy 75 liczb całkowitych sizeof
int. A więc wymaga dwóch parametrów.
Jest to trochę czystsze, ponieważ nie musisz tak naprawdę wykonywać mnożenia.
W malloc trzeba było zrobić 75-krotną imprezę.
Tutaj po prostu mówisz
75, tworzysz 75 int, a rozmiar każdego z nich to 4
bajty lub cokolwiek, co system zwraca z rozmiaru operatora.
Ale znowu znaczenie tego polega na tym, że zainicjuje również wszystkie te dane, wartość zwracana
może być zerowa, jeśli nie jest w stanie przydzielić tej pamięci, której z kolei nie powinieneś przerywać.
Ale jest bardzo podobny do używania malloc, z wyjątkiem dużego plusa to inicjalizacja.
Wszystkie wartości int są inicjalizowane na zero, a to naprawdę duża sprawa,
duża zaleta.
Jest też trochę łatwiejszy w użyciu z tymi dwoma parametrami.
Porozmawiajmy o innej funkcji o nazwie realloc.
Funkcja realloc umożliwia ponowne użycie lub rozszerzenie pamięci, która została wcześniej
przydzielona za pomocą malloc lub calloc.
Jest to więc sposób na dynamiczną zmianę ilości przydzielonej
pamięci podczas pracy.
Możesz więc początkowo stworzyć tak dużo pamięci, a potem zdajesz sobie sprawę,
że to za mało pamięci. Cóż, możesz po prostu zadzwonić do funkcji
realloc, co spowoduje ponowne przydzielenie pamięci.
Oczekuje dwóch wartości argumentów, wskaźnika zawierającego adres, który
został wcześniej zwrócony przez wywołanie malloc.
Masz więc wskaźnik, który ma przydzieloną część pamięci lub calloc, a następnie
rozmiar i bajty nowej pamięci, którą chcesz ją przydzielić.
Więc to przydzieli ilość pamięci, którą określisz drugim argumentem,
to jest drugi argument. Przenosi zawartość wcześniej przydzielonej pamięci,
do której odwołuje się wskaźnik, więc automatycznie wie, ile
pamięci wcześniej przydzieliłeś, ponieważ przekazałeś wskaźnik.
A potem faktycznie doda do tego nowo przydzieloną pamięć.
I zwraca pustą wskazówkę do nowej pamięci.
Więc musisz go przekształcić, gdy dzwonisz do realloc, tak jak inne.
Zwraca wartość null, jeśli nie można było ponownie przydzielić,
co znowu jest prawdopodobnie błędem krytycznym i należy przerwać.
Najważniejszą cechą tej operacji jest to, że
realloc zachowuje zawartość oryginalnego obszaru pamięci, a Ty po prostu rozszerzasz tę pamięć.
Może być pofragmentowany, ponieważ być może nie byłeś w stanie zrobić tego po
kolei, ale nadal masz całą tę pamięć, którą możesz przypisać do danych.
Spójrzmy na przykład.
W tym przykładzie użyjemy malloc, a następnie ponownie przydzielimy pamięć, a
następnie ją zwolnimy.
Więc tutaj tworzymy wskaźnik postaci.
Nie inicjalizujemy go na null, prawdopodobnie powinniśmy.
Ale nie ma przydzielonej żadnej pamięci na to, na co wskazuje.
Ma przydzieloną pamięć do przechowywania wskaźnika,
który jest po prostu adresem. Ale jeśli spróbujesz przejść pod ten adres, twój program będzie hostem.
Więc to, co robisz, nazywasz malloc.
Tutaj mówimy malloc 15.
Spowoduje to utworzenie 15 bajtów na wskaźnik znaku.
Rzucamy to, ponieważ zwraca wskaźnik void. Zatem str jest teraz
wskaźnikiem, który ma przydzieloną pamięć na to, na co wskazuje.
Następnie możemy użyć str copy do przekazania pewnych danych do tego, na
co wskazuje, ciągu znaków
i wypisujemy łańcuch w adresie.
Cóż, wtedy zdajemy sobie sprawę, że to, co przydzieliliśmy 15 bajtów, nie
jest wystarczająco duże.
Następnie wywołujemy realloc i przekazujemy wskaźnik,
którym jest str i chcemy ponownie przydzielić 25 kolejnych bajtów, a następnie zwraca
punkt void, ponieważ musimy go ponownie rzutować.
Teraz nadal będziemy mieć w pamięci Jasona.
Więc teraz możemy wywołać ciąg ciągów konkatenowanych i połączyć je z ciągiem com, więc
teraz będzie to Jason. pl, a następnie drukujemy.
I ostatnia, ale nie mniej ważna rzecz,
prawdopodobnie najważniejsza rzecz w tym slajdzie, więc nie mamy wycieku pamięci, nazywamy to wolnym.
Zawsze dzwoń za darmo, dzwoniąc do malloc.
Spójrzmy więc na ten przykład wewnątrz bloków kodu.
Oto przykład kodu w naszym
edytorze bloków kodu.
Pierwszą rzeczą, na którą powinieneś zwrócić uwagę, jest to, że
dołączamy standardowy plik nagłówkowy lib, ponieważ jest to miejsce, w którym znajdują się
nasze funkcje malloc i nasze funkcje realloc, a także nasza bezpłatna funkcja.
Również dlatego, że używamy niektórych funkcji str, konkatenacji
i kopiowania, dołączamy string. godz.
Teraz w linii ósmej utworzymy wskaźnik postaci.
Nie ma przydzielonej pamięci.
Powinniśmy zainicjować to na null, ponieważ
jeszcze na nic nie wskazuje. W linii 11 dzwonimy do malloc.
I prawdopodobnie powinniśmy powiedzieć 15 razy większy
rozmiar znaku w przypadku, gdy rozmiar
bajtów znaku jest inny, a następnie rzutujemy go
na wskaźnik
znakowy, ponieważ malloc zwraca void. Następnie go kopiujemy.
Gdybyśmy spróbowali to skopiować i nic nie zrobilibyśmy w linii 11.,
program zawiesiłby się, ponieważ nie mamy przydzielonej pamięci dla tego, na co wskazuje łańcuch.
Następnie po prostu drukujemy ciąg i adres.
Teraz nazywamy realloc.
Musimy przechowywać więcej danych niż 15 bajtów,
więc zamierzamy ponownie przydzielić 25
bajtów i prawdopodobnie będziemy chcieli powtórzyć sizeof char, aby
zachować spójność.
Rzucamy to, a następnie łączymy w naszej istniejącej lokalizacji
pamięci, która zawiera Jason. pl i wydrukujemy jason. com.
I wreszcie, w linii 20. uwalniamy tę pamięć, która jest
naprawdę ważna.
Więc chodźmy dalej i uruchommy to. Zobaczysz tylko, że
kiedy drukujemy to po raz drugi, mamy konkatenację.
Więc nasz
pierwszy ciąg, mamy przydzieloną pamięć i jest to adres 000C2918.
Następnie utworzyliśmy nowy adres, przenosząc i przyjmując istniejący rozmiar wskaźnika, który
przekazaliśmy jako argument.
Więc 000C0CD8 ma przydzielone więcej danych,
istniejące dane w
Jason i 25 więcej bajtów.
Więc to, co zauważysz, to konkatenacja.
Ponieważ kiedy przenosiliśmy,
umieściliśmy zawartość Jason w tej pamięci i po prostu ją rozszerzyliśmy.
Tak więc wiele zalet korzystania z tych malloc, tych calloc i bezpłatnych połączeń realloc.
Chcesz ich użyć, chcesz to zrozumieć. Dość proste funkcje w użyciu.
A więc na naszym ostatnim slajdzie przejrzyjmy tylko kilka wskazówek.
Chcesz uniknąć przydzielania dużej ilości małych ilości pamięci, nie jest to wydajne.
Kiedy alokujesz pamięć na stercie,
wiąże się to z pewnym narzutem.
A jeśli przydzielisz małe bloki pamięci, będzie to miało znacznie większy narzut niż przydzielanie
mniejszej liczby większych bloków.
Więc przydziel potrzebną pamięć, ale nie
rób tego co dwie instrukcje z realokacją, rób duże
fragmenty.
Trzymaj się pamięci tylko tak długo, jak jej potrzebujesz. Pamięć to rzadki zasób.
Kiedy jest na stercie, jest jeszcze mniej.
Więc kiedy skończysz z blokiem pamięci, dzwoń za darmo.
Zawsze upewnij się, że udostępniasz zwolnienie przydzielonej pamięci, zdecyduj, gdzie w
kodzie zwolnisz pamięć podczas pisania kodu, który ją przydziela.
Upewnij się, że masz dostęp do tego wskaźnika.
Jeśli chcesz, aby wskaźnik był globalny, musisz to zrobić.
Ale zwykle wszędzie tam, gdzie przydzielasz tę funkcję, powinieneś zwolnić ją w tej funkcji.
Jeśli masz inne funkcje zwalniające
pamięć, zakładasz, że nie jest to dobre. Jest tam za dużo sprzężeń i zależności.
Osoba, która go tworzy, powinna być osobą, która go usuwa.
Nie przerzucaj odpowiedzialności na inne funkcje i tym podobne.
Upewnij się, że przypadkowo nie nadpisałeś adresu pamięci, którą zaalokowałeś na
stercie, zanim ją zwolnisz, spowoduje to wyciek pamięci.
Więc jeśli spróbujesz zapisać w nim zbyt wiele bajtów lub jeśli masz przepełnienia,
możesz mieć problemy.
Dlatego należy zachować szczególną ostrożność podczas przydzielania pamięci w pętli,
ponieważ może to powodować problemy z przepełnieniem bufora.
Mam nadzieję, że to było pomocne. Dynamiczna alokacja pamięci to wielka, wielka sprawa.
Dam ci wyzwanie, które pomoże ci lepiej się z tym zapoznać,
ale upewnij się, że rozumiesz tę koncepcję. Dziękuję Ci.