Hallo und Willkommen. Nachdem wir die dynamische Speicherzuordnung verstanden
haben, müssen wir sie implementieren und wissen, wie sie in c-Code ausgeführt wird.
Wir werden also über drei wirklich wichtige Funktionen
sprechen: malloc, calloc, realloc.
Sie sehen, dass sie sozusagen Namen haben, bei denen die
Speicherzuweisung aufgerufen und neu zugewiesen wird.
Die einfachste und gebräuchlichste Methode zum Zuweisen
von Speicher ist die Standardbibliotheksfunktion malloc.
Dadurch wird zur Laufzeit dynamischer Speicher zugewiesen.
Um diese Funktion aufzurufen, müssen Sie die Standard-lib-Headerdatei einschließen.
Sie geben die Anzahl der Speicherbytes an, die Sie als Argument zuweisen möchten.
Von dieser Funktion wird die Adresse des ersten zugewiesenen Speicherbytes zurückgegeben.
Da Sie eine Adresse zurückerhalten, ist ein Zeiger der einzige Ort, an dem Sie sie platzieren können.
Aus diesem Grund ist eine dynamische Speicherzuweisung nur über dieses
Konzept eines Zeigers möglich, da er Adressen zurückgibt.
Sie können nur eine Adresse in einen Zeiger einfügen, das speichert ein Zeiger, ein Zeiger speichert eine Adresse.
So machen Sie es also, sehr einfache Syntax.
Wenn Sie Ihren Zeiger erstellen, müssen Sie dies normalerweise nicht tun.
Sie können Ihren Zeiger erstellen und auf null setzen.
Und dann in der nächsten Zeile könnten Sie malloc.
Aber hier ist die Standardsyntax, mit der Sie Ihren int-Zeiger erstellen, Zeiger
auf einen Typ int.
Und dann werden Sie feststellen, dass Sie malloc anrufen.
Und hier sagen wir malloc, wir werden 100 Bytes für malloc
erstellen und es dann in einen int-Zeiger umwandeln.
Da malloc nur eine Adresse zurückgibt, müssen Sie sie
in einen Endzeiger umwandeln, da diese Adresse
einen int-Zeiger speichert.
Dies ist die Standardmethode.
Jetzt fordern Sie 100 Byte Speicher
an und weisen ihn der Adresse des Speicherblocks der p-Nummer zu.
pNumber zeigt auf den ersten int-Speicherort am Anfang der 100 zugewiesenen Bytes.
Dies bedeutet, dass Ganzzahlen 4 Bytes pro Stück sind und
25 int-Werte enthalten können.
Dies setzt wiederum voraus, dass Sie 4 Bytes haben.
Abhängig von dem System, das Sie ausführen, können sie jetzt größer oder kleiner sein.
Sie sollten also das Problem damit sehen. Dieser Code funktioniert nicht
auf verschiedenen Systemen, da Sie die Zuordnung basierend auf einer Annahme, basierend
auf der Annahme der int-Größe, vornehmen.
Es ist also nicht wirklich gut, Malloc auf diese
Weise mit einer konstanten Zahl von 100 zu verwenden.
Sie möchten den Operator sizeof verwenden.
Daher ist es besser, die Annahme zu entfernen, dass Ints 4 Bytes sind.
Wenn Sie stattdessen Platz für 25 Ganzzahlen innerhalb des malloc-Arguments zuweisen möchten, geben Sie die
Anzahl der Ganzzahlen an,
die Sie zuweisen möchten, und multiplizieren Sie diese mit der Größe der Ganzzahlen.
Dadurch wird der Code viel portabler.
Dieser Code funktioniert jetzt auf verschiedenen Systemen, verschiedenen Betriebssystemen und verschiedenen Architekturen.
Sie geben dem Rückgabewert immer noch die Adresse, da er auf einen Typ int
zeigen muss, also sagen Sie int star. Der einzige Unterschied besteht darin, dass Sie den Operator sizeof verwenden.
Die Größe des Operators ist sehr wichtig, wenn Sie Speicher mit malloc oder
calloc oder realloc zuweisen.
Sie verwenden immer den Operator sizeof für den
Typ, den Sie zuweisen, und möchten ihn dann mit der Anzahl
der gewünschten Zahlen multiplizieren. Wenn Sie also 25 int speichern
möchten, tun Sie dies mit 25.
Das obige Argument von malloc zeigt also deutlich, dass genügend Bytes zur Aufnahme von
25 Werten vom Typ int verfügbar sein sollten.
Beachten Sie auch, wie ich bereits erwähnte, die Besetzung. Dies konvertiert die von
der Funktion zurückgegebene Adresse in den Typzeiger in int und int Zeiger.
Malloc gibt einen Zeiger vom Typ void zurück, das macht es so nützlich.
Es gibt diese Adresse zurück, aber es ist ein ungültiger Zeiger.
Denken Sie daran, wir haben über leere Zeiger gesprochen und warum leere Zeiger
nützlich sind, da Sie jetzt eine Funktion haben können, die verschiedene Parameter annehmen kann und einen
leeren Zeiger zurückgeben kann. Jetzt sehen Sie die Möglichkeit,
einen ungültigen Zeiger wieder zurückzugeben, aber
um ihn zu verwenden, um ihm tatsächlich Daten zuzuweisen, um ihn zu de-referenzieren, müssen Sie
ihn umwandeln.
Wenn wir dies nicht wirken wollten, könnten wir sagen, dass die Stern-P-Nummer nichtig ist.
Wir besetzen es nicht, aber dann müssten wir es irgendwann
besetzen, wenn wir es de-referenzieren wollten.
Aber deshalb müssen wir aus Malloc werfen. Es gibt eine Adresse zurück und Sie möchten, dass sie auf
einen int-Typ verweist, und es gibt einen ungültigen Typ zurück.
Sie können eine beliebige Anzahl von Bytes anfordern.
Dies hängt jedoch von Ihrem System ab und davon, wie viel Speicher Sie auf Ihrem System haben.
Wenn Sie also versuchen, eine Tonne Bytes zu erstellen oder anzufordern, oder wenn
es eine Einschränkung gibt, kann dies fehlschlagen.
Und wenn es fehlschlägt, gibt es den Wert null zurück, malloc gibt null zurück.
Und das ist gut so, denn Zeiger, wenn Zeiger auf Null zeigen, bedeuten, dass
sie nicht auf einen Speicher zeigen,
sodass Sie keinen Speicher versehentlich beschädigen oder ändern können.
Daher ist es immer eine gute Idee, nach
dem Ausführen eines Mallocs sofort auf Null zu prüfen,
bevor Sie es verwenden, genau
wie beim Zuweisen von Daten oder beim De-Referenzieren eines Zeigers.
Sie möchten sicherstellen, dass Sie tatsächlich auf etwas zeigen.
Hier reservieren wir also etwas Speicher für p-Nummer unseres Zeigers und
dann sagen wir, wenn nicht p-Nummer.
Denken Sie daran, die Null ist eine Null.
Sie können also einfach ein Nicht darauf verwenden. Sie müssen es nicht mit null vergleichen.
Sie können, Sie können es mit null vergleichen, es macht es lesbarer, vielleicht macht es expliziter.
Aber Sie können einfach nicht p-Zahl sagen, und das heißt eigentlich nicht
eins, und null ist null. Dies wird also auf null prüfen.
Und wenn es null ist, bedeutet dies im Grunde, dass
der Speicher nicht zugewiesen werden konnte. Wenn Sie nicht in der Lage sind, Speicher in Ihrem Programm zuzuweisen, sind Sie in Schwierigkeiten.
Was Sie normalerweise in diesem Code tun sollten, um einen Speicherzuordnungsfehler
zu beheben, ist, dass Sie abbrechen, exit beenden
und aus Ihrem Programm aussteigen sollten. Denn wenn Ihr Programm keinen Speicher zuordnen kann, ist Ihr Programm abgespritzt.
In der Regel wird dies als Unfallabbruch bezeichnet, wenn Sie keinen Speicher zuordnen können.
Zumindest wenn Sie dies tun, können Sie eine Meldung anzeigen, die
das Programm beendet. Es ist viel besser,
als zuzulassen, dass das Programm fortgesetzt wird, wenn Sie es nicht waren. Wir können keinen Speicher zuweisen.
Da Sie dann möglicherweise fortfahren, können Sie versuchen, diesen Speicher zu
verwenden. Er wird null sein, sodass das Programm sowieso abstürzt, aber
Sie müssen nicht unbedingt wissen, warum.
Sie müssen sich den Aufrufstapel ansehen. Das Debuggen dieses Fehlers dauert viel länger.
Speicher freigeben. Jetzt habe ich erwähnt, dass
Sie, da Sie der Programmierer sind, der für die Zuweisung des Speichers verantwortlich
ist, den Speicher entfernen müssen, wenn Sie ihn nicht mehr verwenden, da Speicher eine
wichtige Ressource ist, insbesondere auf eingebetteten Systemen.
Sie sollten keinen Speicher verwenden, wenn Sie ihn nicht benötigen. Sie müssen also den Speicher freigeben.
Sie möchten den Speicher immer dann freigeben, wenn er nicht mehr benötigt wird.
Der auf dem Heap zugewiesene Speicher wird automatisch freigegeben, wenn Ihr Programm vorhanden ist. Dies ist
gut, da es einige Bereinigungen durchführt.
Es ist jedoch viel besser, den Speicher explizit freizugeben, wenn Sie damit fertig sind,
auch wenn es kurz vor dem Beenden ist.
Es ist sauberer,
machen Sie es immer kostenlos oder geben Sie den Speicher immer frei, nachdem Sie
ihn zugewiesen haben. Sie möchten diese Regel befolgen. Dies ist eine strenge strenge Regel.
Geben Sie immer Speicher frei, wenn Sie ihn zuweisen.
Ein Speicherverlust tritt auf, wenn Sie Speicher dynamisch zuweisen und die
Referenz nicht beibehalten, sodass Sie sie nicht freigeben können.
Vielleicht haben Sie keinen Spielraum für diese Referenz, die Sie nicht können. Du gibst es nicht frei.
Das nennt man ein Speicherleck. Es ist Speicher zugewiesen, der jedoch nicht mehr verwendet wird.
Dies tritt häufig auf, wenn Sie sich in einer Schleife befinden und Speicher in einer Schleife zuweisen.
Du bist auf dem Laufenden. Sie ordnen diesen Speicher zu. Sie verwenden es innerhalb der
Schleife, springen dann aus der Schleife heraus und lassen es nicht mehr los.
Jede Schleifeniteration weist sie zu.
Um Speicher freizugeben, den Sie dynamisch zugewiesen haben, müssen
Sie weiterhin Zugriff auf diese Adresse haben.
Normalerweise möchten Sie also einen freien Speicher, wenn Sie ihn nicht mehr verwenden, und normalerweise
in der Methode, die Sie ihm zugewiesen haben.
Wenn Sie diesen Speicher jedoch an eine andere Funktion übergeben und es dann
in der Verantwortung dieser Funktion liegt, möchten Sie normalerweise den Speicher und die Funktion,
die Sie ihm zuweisen, freigeben.
Um den Speicher für einen Block dynamisch zugewiesenen Speichers
freizugeben, dessen Adresse Sie in einem Zeiger gespeichert haben, müssen Sie
lediglich die freie Funktion aufrufen.
Dies ist auch in der Standardbibliothek. Sie übergeben einfach den Zeiger auf die freie Funktion.
Und normalerweise möchten Sie diesen Zeiger, nachdem Sie ihn übergeben haben, auf null
setzen, da dies bedeutet, dass Ihnen kein Speicher zugewiesen ist.
Die freie Funktion hat einen formalen Parameter vom Typ
void star, sodass Sie jeden Zeigertyp übergeben
können und es funktioniert.
Sie müssen es nicht wirklich besetzen oder so etwas.
Solange die p-Nummer die Adresse enthält, die bei der Zuweisung des Speichers zurückgegeben wurde, ist
der gesamte Speicherblock für die weitere Verwendung frei. Andere Programme können diesen Speicher verwenden. Ihr Programm
kann jetzt diesen Speicher verwenden. Dies ist
wichtig, wenn Sie sich auf einem eingebetteten System befinden.
Und dann möchten Sie, wie bereits erwähnt, den Zeiger immer
auf null setzen, um anzuzeigen, dass dieser Zeiger nicht mehr auf etwas zeigt.
Diese Erinnerung wurde freigegeben.
Es gibt eine andere nette Funktion namens Calloc.
Dies hat einige Vorteile gegenüber Malloc. Es ist malloc sehr ähnlich,
aber es weist den Speicher als eine Anzahl von Elementen in einer bestimmten Größe
zu und initialisiert auch den Speicher.
Das ist der Unterschied, die Initialisierung. Ich habe über die Bedeutung der Initialisierung gesprochen.
Aus diesem Grund
möchten Sie Daten immer initialisieren, damit Sie verstehen, dass sich dort
Daten befinden, die Sie erwarten.
Wenn Sie Daten nicht initialisieren, befinden sich möglicherweise Daten darin, von denen Sie keine Ahnung haben.
Initialisieren Sie Ihre Daten also immer mit etwas.
Und Calloc ist ein bisschen besser, weil es es für Sie erledigt.
Es dauert zwei Argumente, hat zwei Parameter.
Die Anzahl der Datenelemente, für die Speicherplatz benötigt wird, die Größe jedes Datenelements.
Es ist im Standard-lib-Header deklariert.
Sie nennen es genauso wie Sie Malloc nennen. Sieht fast genauso aus.
In diesem Beispiel erstellen wir
einen Ganzzahlzeiger und weisen 75 Ganzzahlen der Größe
int zu. Es werden also zwei Parameter benötigt.
Es ist ein wenig sauberer, weil Sie keine Multiplikation durchführen müssen.
In malloc mussten Sie ein Ereignis mit einer Zeitgröße von 75 durchführen.
Hier sagen Sie einfach 75, Sie
erstellen 75 Zoll und die Größe für jedes beträgt 4 Byte oder was
auch immer das System von der Größe des Operators zurückgibt.
Die Bedeutung davon ist jedoch, dass auch alle diese Daten initialisiert werden. Der Rückgabewert könnte null
sein, wenn er nicht in der Lage ist, diesen Speicher zuzuweisen, den Sie wiederum nicht abbrechen sollten.
Aber es ist der Verwendung von Malloc sehr ähnlich, außer dass das große Plus diese Initialisierung ist.
Alle Ints werden auf Null initialisiert, und das ist eine wirklich große Sache,
ein großer Vorteil.
Es ist auch ein bisschen einfacher mit den beiden Parametern zu verwenden.
Lassen Sie uns über eine andere Funktion namens Realloc sprechen.
Mit der Realloc-Funktion können Sie Speicher, den Sie zuvor mit malloc oder
calloc zugewiesen haben, wiederverwenden oder erweitern.
Auf diese Weise können Sie dynamisch ändern, wie viel Speicher während der
Ausführung zugewiesen wird.
Sie könnten also zunächst so viel Speicher erstellen, und dann stellen Sie fest,
dass dies nicht genug Speicher ist. Nun, was Sie dort tun können, ist, dass Sie einfach Realloc
aufrufen können, und dies wird Speicher neu zuweisen.
Es werden zwei Argumentwerte erwartet, ein Zeiger, der eine Adresse enthält, die
zuvor durch einen Aufruf von malloc geändert wurde.
Sie haben also einen Zeiger, dem Speicher oder Calloc zugewiesen ist, und dann die Größe
und die Bytes des neuen Speichers, dem Sie ihn zuweisen möchten.
Dadurch wird die Speichermenge zugewiesen, die Sie durch das zweite Argument angegeben
haben. Dies ist das zweite Argument. Es überträgt den Inhalt des zuvor zugewiesenen
Speichers, auf den der Zeiger verweist, und weiß automatisch, wie viel Speicher Sie
zuvor zugewiesen haben, weil Sie den Zeiger übergeben.
Und dann wird tatsächlich der neu zugewiesene Speicher hinzugefügt.
Und es gibt einen leeren Zeiger auf den neuen Speicher zurück.
Sie müssen es also neu fassen, wenn Sie realloc wie die anderen aufrufen.
Es gibt null zurück, wenn Sie nicht neu zuordnen konnten, was
wiederum wahrscheinlich ein schwerwiegender Fehler ist und Sie abbrechen sollten.
Das wichtigste Merkmal dieser Operation ist, dass bei
der Neuzuweisung der Inhalt des ursprünglichen Speicherbereichs erhalten bleibt. Sie erweitern lediglich diesen Speicher.
Es kann fragmentiert sein, weil Sie es möglicherweise nicht nacheinander ausführen konnten, aber Sie
haben immer noch den gesamten Speicher, den Sie jetzt Daten zuweisen können.
Schauen wir uns ein Beispiel an.
In diesem Beispiel verwenden wir malloc und ordnen dann den Speicher neu zu und
geben ihn dann frei.
Hier erstellen wir also einen Zeichenzeiger.
Wir initialisieren es nicht auf null, wir sollten es wahrscheinlich sein.
Es ist jedoch kein Speicher für das zugeordnet, auf das es zeigt.
Es ist Speicher zum Speichern des Zeigers
zugewiesen, der nur eine Adresse ist. Wenn Sie jedoch versuchen, zu dieser Adresse zu wechseln, wird Ihr Programm gehostet.
Sie nennen Malloc.
Hier sagen wir Malloc 15.
Dadurch werden 15 Bytes für einen Zeichenzeiger erstellt.
Wir haben es gewirkt, weil es einen leeren Zeiger zurückgibt. Str ist also jetzt ein
Zeiger, dem Speicher für das zugewiesen ist, auf das er zeigt.
Wir können dann str copy verwenden, um einige Daten an das zu übergeben, auf das sie
zeigen, eine Zeichenfolge, und wir
drucken die Zeichenfolge in der Adresse aus.
Nun, dann stellen wir fest, dass das, was wir 15 Bytes zugewiesen haben,
nicht groß genug ist.
Dann rufen wir realloc auf und übergeben den
Zeiger str, der str ist, und wir möchten 25 weitere Bytes neu zuordnen. Dann wird ein
ungültiger Punkt zurückgegeben, da wir ihn erneut umsetzen müssen.
Jetzt werden wir noch Jason im Gedächtnis haben.
Jetzt können wir String verkettet aufrufen und uns mit String com verketten.
Jetzt ist es Jason. com und dann drucken wir es aus.
Und zu guter Letzt ist es wahrscheinlich
das Wichtigste auf dieser Folie, dass wir keinen Speicherverlust haben, wenn wir kostenlos anrufen.
Rufen Sie immer kostenlos an, wenn Sie malloc anrufen.
Schauen wir uns dieses Beispiel in Codeblöcken an.
Hier ist unser Codebeispiel
in unserem Codeblock-Editor.
Und das erste, was Sie beachten sollten,
ist, dass wir die Standard-lib-Header-Datei einschließen, da sich dort unsere
Malloc-Funktionen und unsere Realloc-Funktionen sowie unsere kostenlose Funktion befinden.
Auch weil wir einige str-Funktionen, Verkettung und Kopieren verwenden,
schließen wir string ein. h.
Jetzt in Zeile acht erstellen wir unseren Zeichenzeiger.
Es ist kein Speicher zugewiesen.
Wir sollten dies auf null initialisieren, da
es noch auf nichts zeigt. In Zeile 11 rufen wir malloc an.
Und wir sollten wahrscheinlich die 15-fache Größe
von char sagen, falls die Größe der
Bytes von char unterschiedlich ist, und sie dann in
einen Zeichenzeiger umwandeln,
weil malloc eine Leere zurückgibt. Wir kopieren es dann.
Wenn wir versuchen würden, dies zu kopieren, und in Zeile 11 nichts tun
würden, würde das Programm abstürzen, da uns kein Speicher zugewiesen ist, auf den die Zeichenfolge verweist.
Wir drucken dann einfach die Zeichenfolge und die Adresse aus.
Jetzt nennen wir realloc.
Wir müssen mehr Daten als 15 Bytes speichern, also
werden wir 25 Bytes neu zuweisen und
wir möchten wahrscheinlich erneut die Größe von char ausführen, um konsistent
zu sein.
Wir setzen dies um und verketten es dann mit unserem
vorhandenen Speicherort, der Jason enthält. com, und wir werden Jason ausdrucken. com.
Und zu guter Letzt geben wir in Zeile 20 den wirklich
wichtigen Speicher frei.
Also lasst uns weitermachen und das ausführen. Und was Sie nur sehen werden,
ist, dass wir die Verkettung haben, wenn wir es das zweite Mal ausdrucken.
Unsere erste
Zeichenfolge hat also Speicher zugewiesen und es ist eine Adresse 000C2918.
Dann haben wir eine neue Adresse erstellt, indem wir die vorhandene Größe des Zeigers, den wir übergeben haben, neu
zugewiesen und als Argument verwendet haben.
000C0CD8 sind also tatsächlich mehr Daten zugeordnet,
die vorhandenen Daten
in Jason und 25 weitere Bytes.
Was Sie also bemerken, ist, dass die Verkettung funktioniert.
Denn als wir neu zugewiesen
haben, haben wir den Inhalt Jason in diesen Speicher gestellt und ihn einfach erweitert.
Es gibt also viele Vorteile bei der Verwendung dieser Malloc-, Calloc- und kostenlosen Realloc-Anrufe.
Sie möchten diese verwenden, Sie möchten diese verstehen. Ziemlich einfache Funktionen zu bedienen.
Lassen Sie uns in unserer letzten Folie einige Richtlinien durchgehen.
Wenn Sie vermeiden möchten, viele kleine Speichermengen zuzuweisen, ist dies nicht effizient.
Wenn Sie Speicher auf dem
Heap zuweisen, hat dies einen gewissen Overhead.
Und wenn Sie kleine Speicherblöcke zuweisen, ist der Aufwand viel höher als bei der
Zuweisung weniger größerer Blöcke.
Ordnen Sie also den Speicher zu, den Sie
benötigen, aber machen Sie es nicht alle zwei Anweisungen mit Neuzuweisung, sondern machen Sie
große Blöcke.
Halten Sie den Speicher nur so lange fest, wie Sie ihn benötigen. Speicher ist eine knappe Ressource.
Wenn es auf dem Haufen ist, ist es noch seltener.
Wenn Sie mit einem Speicherblock fertig sind, rufen Sie kostenlos an.
Stellen Sie immer sicher, dass Sie den von Ihnen zugewiesenen Speicher freigeben, und entscheiden Sie, wo in
Ihrem Code der Speicher freigegeben wird, wenn Sie den Code schreiben, der ihn zuweist.
Stellen Sie sicher, dass Sie Zugriff auf diesen Zeiger haben.
Wenn Sie den Zeiger global machen müssen, müssen Sie dies tun.
Aber normalerweise sollten Sie es in dieser Funktion freigeben, wo immer Sie es in dieser Funktion zuweisen.
Wenn Sie andere Funktionen haben, die Speicher für
Sie freigeben, gehen Sie davon aus, dass dies nicht gut ist. Es gibt dort zu viele Kopplungen und Abhängigkeiten.
Die Person, die es erstellt, sollte die Person sein, die es löscht.
Übertragen Sie die Verantwortung nicht auf andere Funktionen und dergleichen.
Stellen Sie sicher, dass Sie die Adresse des Speichers, den Sie auf dem Heap zugewiesen haben,
nicht versehentlich überschreiben, bevor Sie ihn freigegeben haben. Dies führt zu einem Speicherverlust.
Wenn Sie also versuchen, zu viele Bytes darauf zu schreiben, oder wenn Sie Überläufe haben,
können Probleme auftreten.
Seien Sie daher besonders vorsichtig, wenn Sie Speicher innerhalb einer Schleife
zuweisen, da dies zu Problemen bei Pufferüberläufen führen kann.
Ich hoffe das war hilfreich. Dynamische Speicherzuweisung eine große Sache.
Ich werde Ihnen eine Herausforderung geben, die Ihnen hilft, sich damit vertraut zu machen,
aber stellen Sie sicher, dass Sie dieses Konzept verstehen. Vielen Dank.