Ubungspaket 29 ¨
Dynamische Speicherverwaltung: malloc() und free()
Ubungsziele: ¨
In diesem ¨ Ubungspaket ¨ uben wir das dynamische Alloziieren 1. und Freigeben von Speicherbereichen
2. von Zeichenketten 3. beliebiger Arrays Skript:
Kapitel: 69 Semester:
Wintersemester 2021/22 Betreuer:
Thomas, Tim und Ralf Synopsis:
Endlich wird es spannend :-) Einige von euch haben schon bemerkt,
dass man bei den Arrays immer schon vorher wissen muss, wie viele
Elemente man ben¨ otigt. Manchmal weiß man das aber nicht. In diesem
Ubungspaket ¨ ¨ uben wir, Speicher dynamisch zu alloziieren und auch
wieder freizugeben. Wenn man sich daran erst einmal gew¨ ohnt hat, ist
es ganz praktisch. Aber das ist ja immer so . . .
Teil I: Stoffwiederholung
Aufgabe 1: Motivation
Auf den ersten Blick mag die dynamische Speicherverwaltung ¨uberfl¨ussig wie ein Kropf zu sein, denn wir k¨onnen ja alle Variablen, insbesondere Arrays, schon zur ¨Ubersetzungszeit festlegen. Erste Antwort: sie ist nat¨urlich nicht ¨uberfl¨ussig, w¨urden wir sie sonst behan- deln ;-) ? ¨Uberlege und beschreibe in eigenen Worten, in welchen F¨allen eine dynamische Speicherverwaltung notwendig bzw. n¨utzlich ist.
Die Antwort steht eigentlich schon in der Fragestellung:
”denn wir k¨onnen ja alle Va- riablen insbesondere Arrays schon zur ¨Ubersetzungszeit festlegen.“ Das k¨onnen wir in vielen F¨allen n¨amlich genau nicht. Beispielsweise wissen wir nicht, wie viele Zahlen wir im Rahmen des Programms verarbeiten m¨ussen. Oder wir wissen nicht, wie lang die zu verarbeitenden Zeichenketten sind. Das heißt eben auch, dass wir die Gr¨oße eines Ar- rays eben nicht immer von vornherein wissen und unsere Arrays beim Schreiben des Programms definieren k¨onnen. Entsprechend m¨ussen wir uns damit abfinden, dass die dynamische Speicherverwaltung insbesondere bei Arrays auch bei uns zur Anwendung kommt.
Aufgabe 2: Fragen zu den Grundlagen
F¨ur die dynamische Speicherverwaltung verwenden wir im Wesentlichen zwei Funktionen.
Wie heißen diese Funktionen und welche Signaturen haben sie?
Dynamische Speicherallokation: void * malloc( int size ) Dynamische Speicherfreigabe: void free( void * ptr )
Welche Datei muss man f¨ur diese Funktionen einbinden? #include <stdlib.h>
In welchem Segment liegt der neue Speicher? Auf dem Heap
Wie/in welcher Form wird der Speicher zur¨uckgegeben? Als Zeiger auf das erste Byte Welchen Wert hat der Zeiger im Fehlerfalle? Es ist ein Null-Zeiger
Aufgabe 3: Ein einfaches Beispiel
Im Folgenden betrachten wir eine kleine, nutzlose Beispielanwendung. Diese Anwendung fragt den Benutzer nach der Zahl der Parameter, die er ben¨otigt. Anschließend alloziiert das Programm ein entsprechendes Array und, sofern dies erfolgreich war, initialisiert es die Elemente mit den Werten 0, 2, 4, . . .
1 # i n c l u d e < s t d i o . h >
2 # i n c l u d e < s t d l i b . h >
3
4 int m a i n ( int argc , c h a r ** a r g v )
5 {
6 int i , size , * p ;
7 p r i n t f ( " wie v i e l e int ’ s b e n o e t i g s t du ? " ) ; 8 s c a n f ( " % d " , & s i z e ) ;
9 if ( s i z e > 0 )
10 {
11 p = m a l l o c ( s i z e * s i z e o f ( int ) ) ;
12 for ( i = 0; i < s i z e ; i ++ )
13 p [ i ] = i * 2;
14 p r i n t f ( " K o n t r o l l a u s g a b e : " ) ;
15 for ( i = 0; i < s i z e ; i ++ )
16 p r i n t f ( " % d " , p [ i ] ) ; 17 p r i n t f ( " \ n " ) ;
18 }
19 e l s e p r i n t f ( " sorry , da k a n n ich n i c h t h e l f e n \ n " ) ;
20 }
Wir wollen nun von euch, dass ihr ein Speicherbildchen malt, in denen die Variableni,size und p enthalten sind. Ebenso wollen wir den neu alloziierten Speicherbereich sowie die in den Zeilen12 und 13 generierten Initialwerte sehen. F¨ur das Beispiel nehmen wir an, dass der Nutzer die Zahl 3 eingegeben hat. Mit anderen Worten: Wie sieht der Arbeitsspeicher unmittelbar vor Ausf¨uhrung der Programmzeile 14 aus?
Segment: Stack
Adresse Variable Wert
0xFE28 int * p : 0xF040 0xFE24 int size : 3
0xFE20 int i : 3
Segment: Heap
Adresse Variable Wert
0xF048 int [ 2 ] : 4 0xF044 int [ 1 ] : 2 0xF040 int [ 0 ] : 0
Teil II: Quiz
Aufgabe 1: Speicherallokation
In der folgenden Tabelle stehen in den drei Spalten der Reihe nach das auszuf¨uhrende Kom- mando, die angelegte Datenstruktur und die Zahl der Bytes, die diese im Arbeitsspeicher belegt. Dabei gehen wir davon aus, dass ein char 1 Byte, ein int sowie ein Zeiger jeweils 4 Bytes und ein double 8 Bytes im Arbeitsspeicher belegt. Vervollst¨andige die fehlenden Spalten. Im Sinne eines etwas erh¨ohten Anspruchs haben wir noch die folgende Struktur:
1 s t r u c t bla { int b l a _ b l a ; d o u b l e b l u b _ b l u b ; };
malloc()-Aufruf Datenstruktur Gr¨oße
malloc(3 * sizeof( int )) Array mit 3 ints 12
malloc(5 * sizeof( double )) Array mit 5 double 40
malloc( 12 ) Vermutlich ein Array mit 12 char 12
malloc(2 * sizeof(struct bla)) Array mit 2 struct bla Elementen 24 malloc(2 * sizeof(struct bla *)) Array mit 2 Zeigern auf auf Elemente
vom Typ struct bla
8 malloc(5 * sizeof(char *)) Array mit 5 char-Zeigern 20
malloc(13) ein char-Array 13
malloc(6 * sizeof(double **)) ein Array mit 6 Zeigern auf double- Zeiger
24
Aufgabe 2: Speicherfreigabe
Erkl¨are mit eigenen Worten, was folgende Funktion macht:
1 v o i d t s t _ a n d _ f r e e ( v o i d * p )
2 {
3 if ( p != 0 )
4 f r e e ( p ) ;
5 }
Diese Funktion gibt Speicher wieder frei, da sie offensichtlich die Funktionfree()aufruft.
Allerding testet sie vorab, ob es sich um einen
”regul¨aren“ Zeigerwert handelt. Mit anderen Worten: Diese Funktion vermeidet es, einen Null-Zeiger versehentlich freizugeben, da dies unter Umst¨anden innerhalb von free() zu einem Programmabsturz f¨uhren k¨onnte.
Teil III: Fehlersuche
Aufgabe 1: Fehler bei malloc() und free()
Der ber¨uhmt-ber¨uchtigte Manager Dr. M. al Loc fand die Idee mit den dynamischen Datenstrukturen so spannend, dass er es gleich einmal ausprobierte. Er wollte eine Funktion haben, die ihm ein Array mit size doubles erzeugt, wobei die einzelnen Elemente mit den Werten 0.0, 1.0, . . . initialisiert werden sollen. Am Ende soll der Speicher wieder freigegeben werden. Finde und korrigiere die Fehler:
1 # i n c l u d e < s t d i o . h > // for p r i n t f () und s c a n f () 2 # i n c l u d e < s t d l i b . h > // for m a l l o c () and f r e e () 3
4 d o u b l e * m k _ d a r r a y ( int n )
5 {
6 int i ; d o u b l e d_a ; // c o m p a c t due to s p a c e
7 if (( d_a = m a l l o c ( n ) ) != 0 )
8 for ( i = 0; i < n ; i ++ )
9 d_a [ i ] = i ;
10 r e t u r n d_a ;
11 }
12
13 int m a i n ( int argc , c h a r ** a r g v )
14 {
15 int i , s i z e ; d o u b l e * dp ; // c o m p a c t due to s p a c e 16 p r i n t f ( " wie v i e l e p a r a m e t e r ? " ) ; s c a n f ( " % d " , & s i z e ) ; 17 dp = m k _ d a r r a y ( s i z e ) ;
18 p r i n t f ( " h i e r das e r g e b n i s : " ) ; 19 for ( i = 0; i < s i z e ; i ++ ) 20 p r i n t f ( " % 5 . 2 f " , dp [ i ] ) ; 21 p r i n t f ( " \ n " ) ;
22 f r e e ( dp ) ; // f r e e the m e m o r y
23 f r e e ( dp ) ; // j u s t to be a b s o l u t e l y s u r e
24 r e t u r n 0;
25 }
Zeile Fehler Erl¨auterung Korrektur
6 * fehlt Die Variable d a muss nat¨urlich als Zeiger definiert wer- den, damalloc()einen Zeiger auf einen neuen Speicher- bereich zur¨uckgibt.
*d a
Zeile Fehler Erl¨auterung Korrektur 7 sizeof()
fehlt
Ein ganz typischer Fehler: In diesem Falle w¨urde malloc() nur n Bytes bereitstellen. Wir ben¨otigen aber n * sizeof(double) Bytes, was wir beim Auf- ruf von malloc()unbedingt ber¨ucksichtigen m¨ussen.
sizeof()
. . . .
18 ff. Test auf Null-Zeiger fehlt
In Zeile 17 wird ein neuer Speicherplatz dynamisch alloziiert. Aber dies kann schief gehen, was durch einen Null-Zeiger angezeigt wird. Da man Null-Zeiger nicht dereferenzieren (auf die Inhalte zugreifen) darf, muss der nachfolgende Programmtext geklammert werden.
if () ...
. . . .
22/23 ein free() zu viel
Einen zuvor alloziierten Speicherplatz darf man nur einmal freigeben. Das Ergebnis durch den zweiten Aufruf ist unbestimmt und kann hier oder sp¨ater zu einem Programmabsturz f¨uhren.
ein free() streichen
Programm mit Korrekturen:
1 # i n c l u d e < s t d i o . h > // for p r i n t f () und s c a n f () 2 # i n c l u d e < s t d l i b . h > // for m a l l o c () and f r e e () 3
4 d o u b l e * m k _ d a r r a y ( int n )
5 {
6 int i ; d o u b l e * d_a ; // c o m p a c t due to s p a c e
7 if (( d_a = m a l l o c ( n * s i z e o f ( d o u b l e ) ) ) != 0 )
8 for ( i = 0; i < n ; i ++ )
9 d_a [ i ] = i ;
10 r e t u r n d_a ;
11 }
12
13 int m a i n ( int argc , c h a r ** a r g v )
14 {
15 int i , s i z e ; d o u b l e * dp ; // c o m p a c t due to s p a c e 16 p r i n t f ( " wie v i e l e p a r a m e t e r ? " ) ; s c a n f ( " % d " , & s i z e ) ; 17 if ( dp = m k _ d a r r a y ( s i z e ) )
18 {
19 p r i n t f ( " h i e r das e r g e b n i s : " ) ;
20 for ( i = 0; i < s i z e ; i ++ )
21 p r i n t f ( " % 5 . 2 f " , dp [ i ] ) ; 22 p r i n t f ( " \ n " ) ;
23 f r e e ( dp ) ;
24 }
25 r e t u r n 0;
26 }
Teil IV: Anwendungen
Aufgabe 1: Aneinanderh¨ angen zweier Zeichenketten
1. Aufgabenstellung
Entwickle eine Funktion, die als Eingabeparameter zwei Zeiger auf je eine Zeichen- kette erh¨alt, diese beiden Zeichenketten zu einer neuen Zeichenkette zusammenf¨ugt und das Ergebnis als Zeiger auf diese zur¨uckgibt.
Beispiel: Aufruf: my strcat( "Prof.", "Weiss-Nicht" ) Ergebnis:"Prof. Weiss-Nicht"
Diese Funktion kann wieder einfach mittels des argc/argv-Mechanismus getestet werden.
2. Vor¨uberlegungen
Was k¨onnen wir aus der Aufgabenstellung direkt ablesen?
1. Wir m¨ussen zwei Zeichenketten zusammenf¨ugen, deren L¨angen wir zur ¨Uber- setzungszeit noch nicht wissen.
2. Daraus folgt, dass wir das Ergebnis nicht auf dem Stack ablegen k¨onnen, sondern uns den ben¨otigten Speicherplatz dynamisch vom Heap besorgen m¨ussen.
3. Die neue Zeichenkette muss so lang sein, dass die beiden alten hinein passen, dann noch ein Leerzeichen und am Ende ein Null-Byte.
3. Pflichtenheft
Aufgabe : Funktion zum Verkn¨upfen zweier Zeichenketten. Das Ergebnis wird auf dem Heap abgelegt.
Parameter : Zwei Eingabeparameter p1 und p2 vom Typ char * R¨uckgabewert : Einen Zeiger auf eine neue Zeichenkette vom Typ char * 4. Testdaten
Ahnlich wie in der Aufgabenstellung beschrieben. Bei Verwendung des¨ argc/argv- Mechanismus nehmen wir argv[ 1 ]als ersten Namen und alle weiteren Parameter als zweiten Namen.
Beispiel: Eingabe: ./my-strcat Dr. Peter Maria Ausgabe: Dr. Peter
Dr. Maria
5. Implementierung
Da wir denargc/argv-Mechanismus schon eingehend ge¨ubt haben, k¨onnen wir uns ganz auf die neu zu entwickelnde Funktion konzentrieren. Die wesentlichen Punk- te standen schon in den
”Vor¨uberlegungen“. Wir erhalten zwei Zeichenketten. Die L¨ange der neuen Zeichenkette muss die Summe der beiden eingehenden Zeichen- ketten sein, plus eins f¨ur das Leerzeichen und eins f¨ur das abschließende Null-Byte.
Das l¨asst sich wie folgt ausdr¨ucken:
Funktion zum Aneinanderh¨angen von Zeichenketten Parameter: Pointer to Char: p1, p2
Variablen: Integer: len1, len2 // zwei laengenwerte Pointer to char: res // der rueckgabewert setze len1 = strlen( p1 )
setze len2 = strlen( p2 )
setze res = malloc(len1 + len2 + 2) wenn res 6= 0
dann kopiere p1 an die Stelle res[ 0 ]
kopiere ein Leerzeichen an die Stelle res[ len1 ] kopiere p2 an die Stelle res[len1 + 1]
kopiere ein Null-Byte an die Stelle res[len1 + 1 + len2]
return res
6. Kodierung
Unsere Funktion my strcat():
1 # i n c l u d e < s t d i o . h > // for I / O
2 # i n c l u d e < s t r i n g . h > // for s t r l e n () und s t r c p y () 3 # i n c l u d e < s t d l i b . h > // for m a l l o c ()
4
5 c h a r * m y _ s t r c a t ( c h a r * p1 , c h a r * p2 )
6 {
7 c h a r * res ;
8 int l e n 1 = s t r l e n ( p1 ) , l e n 2 = s t r l e n ( p2 ) ; 9 if ( res = m a l l o c ( l e n 1 + l e n 2 + 2 ) )
10 {
11 s t r c p y ( res , p1 ) ;
12 res [ l e n 1 ] = ’ ’ ;
13 s t r c p y ( res + l e n 1 + 1 , p2 ) ;
14 res [ l e n 1 + l e n 2 + 1] = ’ \0 ’ ;
15 }
16 r e t u r n res ;
17 }
Das ist unser Hauptprogramm zum Testen:
18 int m a i n ( int argc , c h a r ** a r g v )
19 {
20 int i ;
21 c h a r * c o m b i ;
22 for ( i = 2; i < a r g c ; i ++ )
23 if ( c o m b i = m y _ s t r c a t ( a r g v [ 1 ] , a r g v [ i ] ) )
24 {
25 p r i n t f ( " m y _ s t r c a t : ’% s ’\ n " , c o m b i ) ;
26 f r e e ( c o m b i ) ; // o t h e r w i s e : m e m o r y l e a k !
27 }
28 }
Aufgabe 2: Verallgemeinertes strcat()
1. Aufgabenstellung
In dieser Aufgabe soll die Funktion my strcat() der vorherigen Aufgabe erweitert werden. Statt nur zwei Zeichenketten zusammenzuf¨ugen, soll die Funktion diesmal in der Lage sein,
”beliebig“ viele Zeichenketten zu verarbeiten. Die einzelnen Zeichenket- ten sollen wieder mittels eines Leerzeichens voneinander getrennt werden. Nat¨urlich muss die gesamte Zeichenkette wieder mit einem Null-Byte abgeschlossen werden.
Innerhalb dieser ¨Ubungsaufgabe nennen wir diese Funktion my nstrcat()
Beispiel: Bei ¨Ubergabe der drei Parameter "Frau", "Peter" und "Otti" soll das Ergebnis"Frau Peter Otti" lauten.
Zum Testen bedienen wir uns wieder des argc/argv-Mechanismus.
2. Vor¨uberlegungen
Auch hier ist es wieder sinnvoll, ein paar Vor¨uberlegungen anzustellen. Doch diesmal werden wir diese als Fragen formulieren, die ihr beantworten m¨usst.
Erfordert die Aufgabenstel- lung eine dynamische Bear- beitung mittelsmalloc()?
Ja, unbedingt
Falls ja, warum? Anzahl und L¨angen der Zeichenketten sind unbekannt Falls nein, warum nicht? Die Aufgabe w¨are in einem anderen ¨Ubungspaket ;-) Wie ¨ubergeben wir die Zei-
chenketten?
In einem Array verpackt Welchen Datentyp hat die-
ser Parameter?
Array of Pointer to Char: char **
Ben¨otigen wir noch einen zweiten Paramenter?
Ja Falls ja, welchen Typ hat dieser Parameter?
int size Welchen Typ hat die Funk-
tion?
char * Wie lautet die vollst¨andige
Funktionsdeklaration?
char *my nstrcat( char **str arr, int size ) Wie bestimmen wir die
L¨ange einer Zeichenkette?
len = strlen( zeichenkette ) Wie lang ist die Ergebnis-
Zeichenkette?
Psize−1
i=0 strlen( zeichenkette[ i ] )
3. Pflichtenheft
Aufgabe : Funktion zum Verkn¨upfen von n Zeichenketten. Das Ergebnis wird auf dem Heap abgelegt.
Parameter : Zwei Eingabeparameter: char **str arrund int size R¨uckgabewert : Einen Zeiger auf eine neue Zeichenkette vom Typ char * 4. Testdaten
Wir bedienen uns wieder desargc/argv-Mechanismus:
Beispiel: Kommando: ./my-strcat Dr. Peter Maria
Ausgabe: ./my-strcat Dr. Peter Maria alseine Zeichenkette 5. Implementierung
Wir k¨onnen uns gleich auf die Funktion my nstrcat() konzentrieren. Wir m¨ussen der Reihe nach die folgenden Schritte durchf¨uhren: Erstens, Berechnung der Ge- samtl¨ange der neuen Zeichenkette. Diese ist die Summe der Einzell¨angen plus die Anzahl der Zeichenketten (n−1 Leerzeichen als Trenner sowie ein Null-Byte am Ende). Zweitens, Allokation der neuen Zeichenkette. Und drittens, Hineinkopieren der einzelnen Zeichenketten mit jeweils einem anschließenden Leerzeichen. Das letz- te Leerzeichen wird durch ein Null-Byte ersetzt. Bei jedem Kopiervorgang m¨ussen wir in der neuen Zeichenkette um so viele Positionen weiterr¨ucken, wie die zuletzt kopierte Zeichenkette lang war. Das l¨asst sich wie folgt ausdr¨ucken:
Funktion zum Aneinanderh¨angen von Zeichenketten Parameter: Array of Pointer to Char: str arr
Integer: size
Variablen: Integer: i, len, pos setze len = o
f¨ur i = 0 bis size - 1 Schrittweite 1
wiederhole setze len = strlen( str arr[ i ] ) setze res = malloc( len + size )
wenn res 6= 0
dann setze pos = 0
f¨ur i = 0 bis size - 1 Schrittweite 1
wiederhole kopiere str arr[ i ] an die Stelle res[ pos ] setze pos = pos + strlen( str arr[ i ] ) setze res[ pos ] = Leerzeichen
setze pos = pos + 1 setze res[ pos ] = Null-Byte return res
6. Kodierung
1 # i n c l u d e < s t d i o . h > // for I / O
2 # i n c l u d e < s t r i n g . h > // for s t r l e n () und s t r c p y () 3 # i n c l u d e < s t d l i b . h > // for m a l l o c ()
4
5 c h a r * m y _ n s t r c a t ( c h a r ** str_arr , int s i z e )
6 {
7 c h a r * res ;
8 int i , len , pos ;
9 for ( i = 0 , len = s i z e ; i < s i z e ; i ++ ) 10 len += s t r l e n ( s t r _ a r r [ i ] ) ;
11 if ( res = m a l l o c ( len ) )
12 {
13 for ( i = pos = 0; i < s i z e ; i ++ )
14 {
15 s t r c p y ( res + pos , s t r _ a r r [ i ] ) ;
16 pos += s t r l e n ( s t r _ a r r [ i ] ) ;
17 res [ pos ] = ’ ’ ;
18 pos ++; // a d v a n c e to the n e x t p o s i t i o n
19 }
20 res [ pos - 1 ] = ’ \0 ’ ; // r e p l a c e l a s t b l a n k
21 }
22 r e t u r n res ;
23 }
24
25 int m a i n ( int argc , c h a r ** a r g v )
26 {
27 c h a r * c o m b i ;
28 if ( c o m b i = m y _ n s t r c a t ( argv , a r g c ) )
29 {
30 p r i n t f ( " m y _ n s t r c a t : ’% s ’\ n " , c o m b i ) ;
31 f r e e ( c o m b i ) ; // j u s t to get u s e d to it
32 }
33 }
Anmerkung:Wir h¨atten auch auf eine der beiden Variablenlenoderposverzichten k¨onnen. Nur fanden wir es so verst¨andlicher. Und die Einsparung einerint-Variablen f¨allt bei den Zeichenketten nicht ins Gewicht.