• Keine Ergebnisse gefunden

¨Ubungspaket 29 Dynamische Speicherverwaltung: malloc() und free()

N/A
N/A
Protected

Academic year: 2021

Aktie "¨Ubungspaket 29 Dynamische Speicherverwaltung: malloc() und free()"

Copied!
12
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

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 . . .

(2)

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)

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

(4)

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.

(5)

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

(6)

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 }

(7)

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

(8)

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 }

(9)

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 }

(10)

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 ] )

(11)

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

(12)

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.

Referenzen

ÄHNLICHE DOKUMENTE

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

Im ¨ Ubrigen gehen wir wieder davon aus, dass alle Zeiger genau vier Bytes im Arbeitsspeicher belegen... Vervollst¨ andige nun

Aufgabe: Schreibe eine Funktion search str(), die eine gegebene Zeichenkette (to- ken) in einer Tabelle (Array) von Zeichenketten sucht und

Am Ende der ¨ Ubung mag sich der eine oder andere fragen, was man denn nun eigentlich von Zeigern hat, denn Zeiger sind erst einmal nur kompliziert.. Die Antwort ist sehr einfach:

Aufgabe: Schreibe eine Funktion search str(), die eine gegebene Zeichenkette (to- ken) in einer Tabelle (Array) von Zeichenketten sucht und

(groß, Mineralwasser und Saft). Na stole

Lege einen speziellen Speicherbereich für jeden Aufruf einer Funktion an. In sequentiellen Programmiersprachen können diese Speicherbereiche auf dem Keller

An dieser Stelle muß die semiotische Ästhetik den kantischen Denkweg verlassen. Seine These von der Metapho- rizität der ästhetischen Idee ist zwar sehr verlockend, denn