Einführung in die Programmierung zusammengesetzte Datentypen, dynamischer Speicher Arvid Terzibaschian 1
Zusammengesetzte Datentypen 2
Wozu zusammengesetzte Datentypen? Anforderung: Sie sollen ein Kundenverzeichnis anlegen. Für jeden Kunden sollen Kenndaten gespeichert werden: Name Adresse Umsätze der letzten Monate Kundennummer Telefon Wie kann man das Modellieren? 3
Wozu zusammengesetzte Datentypen? Anforderung: Sie sollen ein Kundenverzeichnis anlegen. Wie kann man das Modellieren? Ansatz 1: Eine Array-Variable für jedes Feld (für 256 Kunden) Probleme: Zusammengehörigkeit der Variablen nicht sichergestellt Übergabe an Funktionen nur einzeln möglich Andere Möglichkeit? int kundennummer[256]; char *name[256]; char *adresse[256]; Kunde: Name,Adresse, Telefon Umsätze,Kundennummer 4
Zusammengesetzte Datentypen: structs Anforderung: Sie sollen ein Kundenverzeichnis anlegen. Lösung: structs erlauben das zusammensetzen von Datentypen zu neuen, komplexeren Datentypen Modellierung mit structs: Kunde: Name,Adresse, Telefon Umsätze,Kundennummer int kundennummer[256]; char *name[256]; char *adresse[256]; struct t_kunde { int kundennummer; char name[64]; char adresse[64]; ; dynamischer String: Platz für 63 Zeichen struct t_kunde kunden[256]; 5
Zusammengesetzte Datentypen: structs structs erlauben Zusammensetzen komplexer Datentypen Definition meist außerhalb von Funktionen der Ausdruck struct { definiert immer einen Datentyp: neuer struct-datentyp: struct t_kunde struct t_kunde { int kundennummer; char name[64]; char adresse[64]; ; Elemente des struct-datentyps: Deklaration wie Variablen/Zeiger struct t_kunde kunden[256]; neue (Array-)Variable kunden mit Datentyp struct t_kunde 6
struct-datentypen zusammengesetzte Datentypen sind elementares Konzept der Softwareentwicklung bilden Zusammengehörigkeit bestimmter Daten direkt ab erleichtern das Modellieren von komplexen Problemen viele Dinge der Realität können mit zusammengesetzten Datentypen wesentlich besser modelliert werden als durch elementaren Zahlen und Strings == [10110101001000010]?? erhöhen die Lesbarkeit und damit Wartbarkeit des Quellcodes z.b. ist eine struct-variable kunden wesentlich aussagekräftiger als ein Array für je Adresse, Telefonnummer 7
Arbeiten mit struct-variablen Zugriff auf einzelne Elementvariablen: per Punkte-Operator strcpy(kunde1.name, Bill Gates ); kunde1.kundennummer = 12; Initialisieren von struct-variablen: in Initialwerte in Reihenfolge der struct-elemente angeben Zuweisen von struct-variablen struct kunde_t { int kundennummer; char name[64]; char adresse[64]; ; /*<- neuer Typ */ /* neue Variable: */ struct kunde_t kunde1; struct kunde_t kunde2; Warum einmal strcpy und einmal Bill Gates? struct kunde_t kunde3 = {20, Bill Gates, USA ; strcpy(kunde1.adresse, Zehlendorf ); kunde2 = kunde1; // Kopiert Werte aller Elementvariablen strcpy(kunde1.adresse, Lichtenberg ); Value-Semantik wie bei elementaren Variablen: Kopieren aller Werte Was ergibt printf(kunde2.adresse)? 8
struct und Speicher Elemente der struct-variablen liegen hintereinander im Speicher Beispiel: struct kunde_t kunden[3]; struct kunde_t { int kundennummer; char name[64]; char adresse[64]; ; /*<- neuer Typ */ /* neue Variable: */ struct kunde_t kunde1; struct kunde_t kunde2; Adresse Element Größe 0xF000 kunde[0].kundennummer 4 Byte kunde[0] 0xF000+4 kunde[0].name 64 Byte 0xF000+68 kunde[0].adresse 64 Byte 0xF000+132 kunde[1].kundennummer 4 Byte kunde[1] kunde[1].name 64 Byte kunde[1].adresse 64 Byte 0xF000+264 kunde[2].kundennummer 4 Byte kunde[2] kunde[2].name 64 Byte kunde[2].adresse 64 Byte 9
struct und Funktionen struct kunde_t { int kundennummer; char name[64]; char adresse[64]; ; /*<- neuer Typ */ Parameter von Funktionen mit struct-datentyp void drucke_kunde(struct kunde_t k) { printf( Name: %s,k.name); printf( Adresse: %s,k.adresse); printf( KdNr: %d,k.kundennummer); Bei Aufruf Call-By-Value-Semantik nur Kopie wird Funktion übergeben Rückgabewerte mit struct-datentyp struct kunde_t neuer_kunde() { kunde_t k = {0, Mustermann, Musterstraße 123 ; return k; 10 auch hier wird Werte-Kopie als Ergebnis zurückgegeben
struct und Funktionen struct-parameter werden immer per Call-By-Value übergeben: void drucke_kunde(struct kunde_t k) { printf( Name: %s,k.name); printf( Adresse: %s,k.adresse); printf( KdNr: %d,k.kundennummer); struct kunde_t { int kundennummer; char name[64]; char adresse[64]; ; /*<- neuer Typ */ Folgerungen: int main() { kunde_t k1 = {10, Karl-Heinz, Berlin ; drucke_kunde(k); unnötiges Kopieren von Werten Rechen- und Speicherkosten Funktionen können Werte nicht bearbeiten k1 wird kopiert und dann druck_kunde mit Kopie aufgerufen Alternative: Call-by-Reference-Semantik via struct-zeigern 11
struct und Funktionen Call-By-Reference mit struct-zeigern void drucke_kunde(struct kunde_t *k) { printf( Name: %s,k->name); printf( Adresse: %s,k->adresse); printf( KdNr: %d,k->kundennummer); Folgerungen: kein unnötiges kopieren Veränderungen von struct-variablen durch Funktionen möglich Problem: Zeiger können nicht initialisiert sein Programmierer hat volle Verantwortung struct kunde_t { int kundennummer; char name[64]; char adresse[64]; ; /*<- neuer Typ */ Operator -> greift auf Elemente von struct-zeigern zu Achtung: keine Gültigkeitsprüfung (!= NULL) des Zeigers k int main() { kunde_t k1 = {10, Karl-Heinz, Berlin ; drucke_kunde(&k1); struct-zeiger auf Variable k1 wird übergeben 12
Einschub: Typedef typedef erlaubt Aliasnamen für Datentypen zu erstellen: struct kunde_t { int kundennummer; char *name; char *adresse; ; typedef struct kunde_t customer; alter Datentyp Aliasname für Datentyp Aliasname customer kann wie struct kunde_t verwendet werden: struct kunde_t kunde1; = customer kunde1; 13
struct und Funktionen Erhöhen der Kundennummer mit Call-By-Value typedef struct kunde_t { int kdnr; char name[64]; char adresse[64]; cust; Call-By-Reference (~Pointer) Daten 3x kopiert Daten 0x kopiert cust inc_kundennr(cust c) { c.kdnr++; return c; 2 int main() { cust kunde = {1, Frank, Berlin ; 1 kunde = inc_kundennr(kunde); 3 printf( Nr: %d,kunde.kdnr); void inc_kundennr(cust *c) { c->kdnr++; ; int main() { cust kunde = {1, Frank, Berlin ; inc_kundennr(&kunde); printf( Nr: %d,kunde.kdnr); bei Aufruf der Funktion beim return in der Funktion beim Zuweisen nach der Funktion aber: Compiler kann hier oft optimieren maximale Geschwindigkeit aber: Pointer sind fehleranfälliger 14
structs in structs struct-datentypen können andere structs enthalten: struct schueler_t { const char name[64]; int note; ; struct raum_t { int haus; int etage; int nr; ; struct kurs_t { const char lehrer[64]; const char name[64]; struct schueler_t schueler[30]; struct raum_t raum; ; struct kurs_t k; strcpy(k.name, Mathe ); k.raum.haus = 3; k.schueler[1].note = 1; structs können sich nicht selbst enthalten nur als Zeiger für z.b. dynamische Strukturen 15
Zusammenfassung struct Bilden zusammengesetzte Datentypen wichtig für Modellierung komplexer Probleme structs können structs enthalten Können als Datentypen für Variablen und Arrays verwendet werden Können als Parameter von Funktionen genutzt werden Call-By-Value-Semantik ohne Zeiger Werte-Kopien Call-By-Reference-Semantik mit Zeigern Adress-übergabe 16
Dynamischer Speicher der Heap 17
Dynamischer Speicher: Warum? Daten bisher gespeichert in elementaren Variablen Größe und Anzahl der Variablen vorher festgelegt struct-variablen Größe und Anzahl der Variablen vorher festgelegt Arrays und Strings Größe und Anzahl der Arrays/Strings vorher festgelegt Problem: oft Anzahl Elemente/ Speicherplätze erst zur Laufzeit berechenbar max. Größe anzunehmen Speicherverschwendung 18
Dynamischer Speicher: Warum? Problem: oft ist Größe der benötigten Datenspeicher vor Laufzeit des Programms nicht bekannt Länge von Arrays und Strings erst zur Laufzeit bestimmbar immer max. Größe anzunehmen ist Speicherverschwendung Lösung: Dynamische Speicherreservierung mit malloc() und free() Speicherreservierung on demand Vorteile: optimale Speichernutzung und dynamische Strukturen Nachteile: komplexere Programmlogik fehleranfälligerer Code Speicherverlust möglich (Memory-Leaks) 19
Speicherbereiche eines Programms gesamter Speicher des Programmes. Vom Betriebssystem zugewiesen 20 Programm-Stack: wird automatisch verwaltet alle Variablen Größe und Anzahl der Variablen alle Arrays zur Compile-Time bekannt alle Parameter von Funktionsaufrufen temporäre Werte zur Berechnung Funktionsaufrufsliste (Funktions-Stack) statische Daten konstante Werte z.b. bei Initialisierung von Strings und Variablen statischer Maschinencode kompilierte Anweisungen Inhalt der ausführbaren Maschinencode- Datei Programm-Heap dynamischer Speicherbereich Programm kann Speicher dynamisch vom Was ist das? Betriebssystem anfordern wird durch Programm in C mit malloc() verwaltet kann vom Programm freigegeben werden in C mit free();
Dyn. Speicher vom Heap Anforderung: ganze Zahlen von 1 N in Array speichern Größe des Arrays zur Laufzeit nicht bekannt Lösung: Funktion malloc() nutzen, um Speicher für n Elemente zur Laufzeit zur Verfügung zu stellen: Funktion void* malloc(int nbytes); reserviert n Bytes Speicher vom Heap und gibt Anfangsadresse als Zeiger zurück Stack int n = 25; int i; int *nums; nums = malloc(n * sizeof(int)); for(i = 0;i<n;++i { nums[i] = i; statische Daten statischer Code Heap [~RAM] sizeof(datentyp) bestimmt die Größe eines Datentyps in Byte Erinnere: Zeiger kann wie Array betrachtet werden 21
Dyn. Speicher vom Heap Funktion malloc() nutzen, um Speicher für n Elemente zur Laufzeit zur Verfügung zu stellen: Funktion void* malloc(int nbytes); reserviert n Bytes Speicher vom Heap und gibt Anfangsadresse als Zeiger zurück Problem: int n = 25; int i; int *nums; nums = malloc(n * sizeof(int)); for(i = 0;i<n;++i { nums[i] = i; Stack statische Daten statischer Code Heap [~RAM] Speicher wird nicht automatisch wieder freigegeben Unterschied zu klassischen Variablen und Arrays (Stackvariablen) Memory-Leaks sind die Folge Lösung: Funktion free() zum Freigeben von Speicher 22
Dyn. Speicher vom Heap Speicher alloziieren und wieder freigeben: Speicher für 25 int-werte auf Heap reservieren Werte wie ein Array nutzen Speicher freigeben (optional: Zeiger nums als invalid kennzeichnen) int n = 25; int i; int *nums; nums = malloc(n * sizeof(int)); for(i = 0;i<n;++i { nums[i] = i; free(nums); nums = NULL; Stack statische Daten statischer Code Heap [~RAM] free erwartet Zeiger auf Anfang des Speicherbereichs Programmierer hat volle Verantwortung, free() auch aufzurufen Grundregel: zu jedem malloc() gehört [irgendwann] ein free() 23
Speicher vom Heap: Probleme Grundsätzliches Problem: Speicher vom Heap muss Programm verwalten zu jedem malloc() muss irgendwann ein passendes free() aufgerufen werden sonst: Memory-Leaks und Segmentation-Fault Für alle Programmiersprachen gilt: Es gibt keine triviale Lösung zur dynamischen Speicherverwaltung Probleme treten immer dann auf, wenn Speicher zu früh, gar nicht, oder mehrmals freigegeben wird Lösungsansatz: Einsatz von Zeigern und Speicher vom Heap so wenig wie möglich & so viel wie nötig. bei Funktionen Call-By-Value sicherer als Call-By-Pointer bei dynamische Arrays/Strings reichen oft auch temporäre Array-Variablen mit fester Maximallänger Stack statische Daten statischer Code Heap [~RAM] 24
Dynamischer Speicher und Zur Erinnerung: Strings = Char-Arrays = Zeiger auf das erste Element dynamische Strings können auch auf dem Heap gespeichert werden Beispiel: char tmp[2048]; printf( Wie geht s? ); fgets(tmp,2048,stdin); printf(tmp); vs char *tmp = malloc(2048*sizeof(char)); printf( Wie geht s? ); fgets(tmp,2048,stdin); printf(tmp); free(tmp); tmp ist char-array-variable Speicher im Stack automatisch gelöscht Nachteil: Stack-Speicher oft begrenzt 25 tmp ist Zeiger Speicher vom Heap mit malloc Nachteil: manuelles löschen mit free pflicht Vorteil: Heap-Speicher viel größer als Stack
Dynamischer Speicher und Strukturen Speicher alloziieren und freigeben funktioniert analog zu elementaren Typen: Speicher für 128 Kunden auf Heap reservieren Wie ein Array nutzen Speicher freigeben Gibt es in diesem Beispiel ein Problem? Problem: dynamischer Speicher und Rückgabewert: ohne free: Memory-Leak? typedef struct kunde_t { int kdnr; char name[64]; char adresse[64]; cust; cust* new_database(int n) { int i; cust *kunden; kunden = malloc(n * sizeof(cust)); for(i = 0;i<n;++i) { sprintf(kunden[i].name, Kunde %d,i); free(kunden); return kunden; mit free: Absturz des Programmes 26
Dynamischer Speicher und Rückgabewerte Frage der Zuständigkeit Wer gibt den Speicher wieder frei? Nicht trivial zu lösen. cust* create_database(int n) { int i; cust *kunden; kunden = malloc(n * sizeof(cust)); for(i = 0;i<n;++i) { sprintf(kunden[i].name, Kunde %d,i); free(kunden); return kunden; 27 typedef struct kunde_t { int kdnr; char name[64]; char adresse[64]; cust; void delete_database(cust* db) { if(db!= NULL) free(db); möglicher Lösungsansatz : Funktionspaar ähnlich malloc() und free() dynamische Speicherallokationsfunktionen mit create_**** Funktion reserviert Speicher auf Heap und gibt Datenstruktur zurück dynamische Speicherfreigabefunktionen mit delete_***** Funktion löscht Speicherreservierung für bestimmte Datenstruktur Aufrufer der create_* und delete_*-funktionen ist zuständig
Zusammenfassung: Dynamischer Speicher Programm kann dynamisch Speicher auf Heap anfordern malloc(n*sizeof(datentyp)) alloziiert Speicher für ein Array mit n Elementen sowohl elementare als auch zusammengesetzte Datentypen erlaubt free(adresse) gibt Speicher wieder frei jedem malloc() sein free() ohne free(): Memory-Leak mehr als ein free(): Programmabsturz dynamische Speicherverwaltung benötigt Zeiger Zeiger = typische Fehlerquelle Programmierer trägt volle Verantwortung für korrekten Programmcode korrektes Arbeiten mit dynamischem Speicher ist niemals trivial! sollte nur bei Alternativlosigkeit eingesetzt werden 28
Vielen Dank! Bei Fragen einfach eine Mail an: arvid@terzibaschian.de 29