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