C Tutorium
– Memory Management –
Knut Stolze
Agenda
Einführung in die Speicherverwaltung
Stack vs. Heap
Malloc
Free
Sizeof
Tipps/Hinweise
2
Speicherverwaltung
Speicher (RAM) ist (meist) liniar und ein
zusammenhängender Bereich
– Muss unterteilt/strukturiert werden
Alle Informationen (Daten und Programme)
müssen im Hauptspeicher abgelegt werden
Virtualisierung von Speicher
– Erweiterung des physisch vorhandenen Hauptspeichers
um Paging/Swap Space
– Betriebssystem kümmert sich um Paging/Swapping
3
Speicherverwaltung (2)
Programme können parallel laufen
– Jedes Programm ist unabhängig von anderen
– Speicher muss zugeteilt/reserviert werden
– Jede Variable in einem Programm muss in
einem zuvor reservierten Speicherbereich
abgelegt werden!
Prozess 1
Prozess 3
Prozess 3
Gesamter (virtueller) Speicher
4
Stack vs. Heap
Stack-Pointer
Heap
Stack
Speicher, der einem Programm
insgesamt zur Verfügung steht
5
Stack vs. Heap (2)
Stack und Heap teilen sich gesamten zur
Verfügung stehenden Speicher
– Pro Prozess
Heap: dynamisch benötigter Speicher
Stack: statisch verwendeter Speicher
6
Stack
Für alle statischen Informationen während des
Programmablaufs
– Wird zur Laufzeit allokiert – nicht beim Starten!
Aufrufinformationen
– Welche Funktion wurde von wo aufgerufen
– Parameter der aufgerufenen Funktion
– Rücksprungadresse
Statische Variablen in der Funktion
– Z.B. int a[50]
Genügend Speicher für 50 “int” Werte
– Werden automatisch beim Verlassen der Funktion
aufgeräumt, d.h. der Speicher wird wieder freigegeben
7
Stack (2)
Andere Programmiersprachen
– C++: Destruktor von Objekten wird aufgerufen
und Speicher wird freigegeben
– Java: Reference count von Objekten auf dem
Stack wird reduziert
garbage collector räumt auf
Allokation auf dem Stack ist
schneller/performanter als vom Heap
– Heap kann Fragmentieren; Stack nicht
8
Heap
Für dynamisch allokierten Speicher
Wird vom Betriebssystem (OS) verwaltet
– Funktion “malloc” fordert Speicherblock an
Passender Block muss gesucht, reserviert und zurückgegeben
werden
– Funktion “free” gibt zuvor angeforderten Speicherblock
wieder frei
OS verwaltet alle angeforderten Speicherblöcke
Speicher des Heaps kann fragmentieren
9
Ulimit (Unix)
Legt Maximum von Resourcen für einen Prozess
fest:
–
–
–
–
–
–
Maximal nutzbarer Speicherbereich (virtuell)
Größe des Stacks
Größe von “core” Dateien
Größe des “data segment” eines Prozesses
Größe von Dateien, die ein Prozess anlegen kann
Größe des ge”pin”ten SpeicherbereichsAnzahl der
geöffneten Dateien
– Blockgröße bei Pipes
– Anzahl der Prozesse eines Nutzers
– CPU-Zeit
10
Malloc
Fordere einen Speicherblock vom Heap an
ptr = malloc(size);
– “ptr” ist ein Zeiger auf den Beginn des
Speicherblocks
Ein Zeiger ist eine Adresse im Hauptspeicher
– “size” ist die Größe des Blocks in Anzahl von
Bytes
ptr
(0x01234567)
Speicherblock
Heap im Hauptspeicher
11
Malloc (2)
Gibt Zeiger vom Typ “void *” zurück
– Typ der Werte, die im Speicherblock hinterlegt werden
sollen ist “malloc” nicht bekannt
– Typ muss mittels Cast umgewandelt werden
int *ptr = NULL;
ptr = (int *)malloc(size);
Es darf nicht ausserhalb des allokierten
Speicherblocks zugegriffen werden
– Speicher könnte anderen Prozessen oder anderen
Datenstrukturen des gleichen Prozesses gehören
12
Malloc (3)
Ähnliche Systemfunktionen:
– calloc
Andere Programmiersprachen verwenden
ähnliche Operatoren, die Typisierung gleich
mitliefern, d.h. der Cast wird intern gleich
mit erledigt:
– C++: Class *object = new Class();
– Java: Class object = new Class();
13
Grundlagen von
Zeiger-Arithmetik
Ergebnis von “malloc” zeigt auf 1 Element des
spezifizierten Datentyps
ptr = (int *)malloc(size);
– “ptr” zeigt auf ein “int”-Wert
Zeigerarithmetik arbeitet grundsätzlich auf dem zu
Grunde liegenden Datentyp
– “ptr = ptr + 1;” lässt den Zeiger auf den nächsten “int”-
Wert zeigen (und nicht auf das zweite Byte)
14
Free
Angeforderte Speicherblöcke müssen wieder
freigegeben werden
free(ptr);
– Sobald nicht mehr gebraucht
– Geschieht automatisch beim Programmende
Zeiger auf Speicherblock darf bis zum “free” nicht
verloren gehen!
Ein Block kann nur genau 1x freigegeben werden
– Mehrfache “free” Operationen könnten einen falschen
Block freigeben
– Danach darf Block nicht mehr verwendet werden
15
Free (2)
Freigegebener Block (oder ein Teil davon)
kann beim nächsten “malloc” wieder
vergeben werden
Andere Programmiersprachen verwenden
intern auch “free”:
– C++: delete-Operator
– Java: garbage collection
16
Sizeof Operator
Anzahl der Bytes von Datentypen kann von
Plattform zu Plattform variieren
– Z.B. unterschiedliche Größen von “int” Werten (32 vs.
64 Bit Prozessoren)
– Unterschiedliche Optimierungsstrategien der Compiler
– Unterschiedliche Mächtigkeit/Performance der
Adressierungsbefehle des Prozessors
– Zugriff auf Speicheradressen, die ein Vielfaches von 4
Bytes sind, oft schneller als Adressierung einzelner
Bytes
17
Sizeof Operator (2)
Padding-Bytes
können vom Compiler
eingeschoben werden
Genaue Größe von Strukturen oft nicht
bekannt oder soll nicht hart-verdrahtet
werden (Portabilität)
18
Sizeof Operator (3)
“sizeof” berechnet Größe eines Wertes oder eines
Datentyps
size = sizeof(int);
size = sizeof(struct myStruct);
size = sizeof myValue;
– Anzahl an Bytes, die die physisch Repräsentation im
Speicher benötigt
ptr = (int *)malloc(N * sizeof(int));
Kann bereits zur Übersetzungszeit errechnet
werden und belastet somit nicht die Laufzeit
19
Realloc
Vorgrößern von Speicherblöcken ist nicht direkt
möglich
– Andere Speicherblöcke können physisch im Speicher
direkt dahinter liegen
int *ptr = NULL;
int *biggerPtr = NULL;
ptr = (int *)malloc(orig_size);
…
biggerPtr = (int *)realloc(ptr, new_size);
20
Memset, memcpy, memmove
Angeforderte Speicherblöcke (malloc) sollten
immer initialisiert werden
memset(ptr, 0x00, size);
– Bei sicherheitskritischen Anwendung ist ein “Leeren”
vor dem Freigeben auch oft ratsam!
Kopieren zwischen überlappungsfreien
Speicherblöcken
memcpy(destination, source, size);
Kopieren zwischen überlappenden
Speicherblöcken
memmove(destination, source, size);
21
Richtlinien & Tipps
Speicherallokation kann fehl schlagen!!
– Ergebnis ist dann NULL-Zeiger
– Ergebnis muss immer überprüft werden
Speicherblock nach “free” nicht weiter
verwenden
– Am besten Zeiger auf NULL setzen:
free(ptr);
ptr = NULL
22
Richtlinien & Tipps (2)
Alle Variablen sofort bei der Deklaration
initialisieren
char *str = NULL;
int counter = 0;
str = (char *)malloc(strlen(orig_str)+1);
if (!str) { … }
memset(str, 0x00, strlen(orig_str)+1);
23
Potientielle Probleme
Keine gute Kontrolle über Speicher
– Keine Gruppierung von Speicher
– Keine Fehlermeldung beim falschen Freigeben
Programm stürzt eventuell ab; vielleicht auch an ganz anderer
Stelle
Buffer Overflow verhindern – Zugriff ausserhalb
des aktuellen Speicherblocks
– Oft keine/unzureichende Prüfung von Überläufen
– Kann sich oft zu Sicherheitsproblemen ausweiten
24
Potentielle Probleme (2)
Memory Leaks – angeforderter Speicher wird nie
freigegeben
– Zeiger auf Speicherblock ist verloren gegangen
Funktionen dürfen keinen Zeiger in den Stack
zurückgeben
– Informationen auf dem Stack sind beim Verlassen der
Funktion nicht mehr gültig
“free” auf Objekt auf dem Stack ist nicht zulässig
– Führt meist zum Absturz des Programms
25