Dynamischer
Speicher
In einer Funktion wird z.B. mit der
Deklaration
int i;
Speicher auf dem sogenannten
Stack reserviert. Wenn die Funktion
verlassen wird (nach dem Aufruf
der Funktion), ist dieser
Speicherbereich automatisch wieder
freigegeben (nicht mehr reserviert).
Beispiel:
int main(){
f();
//...
Erst wenn f aufgerufen wird, wird
Speicher für myfeld auf dem Stack
reserviert.
Wieviel Byte sind dies ?
}
void f(){
int myfeld[10];
//...
}
10 · Speicherbedarf (integer)
Nach dem Aufruf wird dieser
Speicherplatz automatisch (ohne Zutun
des Programmmierers) wieder
freigegeben.
Beispiel:
Man will die von einem
Anwender (über Tastatur
eingegebene) bestimmte
Anzahl von Zahlen in einem
Feld abspeichern.
int main(){
int myfeld[1000];
//...
}
Welchen Nachteil hat dies bezüglich des Speicherplatzverbrauchs
Der Anwender kann weniger Speicher – als reserviert – verbrauchen
(z.B. bei Eingabe nur einer Zahl).
Dies ist dann Speicherverschwendung.
Um dies zu vermeiden kann der Anwender – während der Laufzeit des
Programms – so viel Speicher reservieren, wie er benötigt.
Im Gegensatz zum Beispiel oben wird diese Reservierung nicht beim
Compilieren, sondern während der Programmlaufs gemacht und heißt
deshalb dynamischer Speicher(reservierung).
Realisierung in C++
int main(){
eingelesener Wert wird in
anz gespeichert
int anz, zahl, i;
int *panf;
Reserviert dynamisch Speicher:
cout << "Anzahl eingeben:";
für ein Feld von anz integer-Werten
cin >> anz;
panf = new int[anz];
cout << "Zahlen eingeben:";
for(i=0; i<anz; i++){
cin >> zahl;
gibt Speicher wieder frei
*(panf+i)=zahl;
Mögliche (gleichwertige)
}
Formen des Zugriffs
// panf[i]=zahl
panf zeigt auf das erste Element
delete panf;
des Feldes
}
int main(){
int anz, zahl, i;
int *panf;
cout << "Anzahl eingeben:";
cin >> anz;
panf = new int[anz];
cout << "Zahlen eingeben:";
for(i=0; i<anz; i++){
cin >> zahl;
*(panf+i)=zahl;
}
//...
panf 020
...
Annahme: anz = 2
WelchenWert hat
panf an dieser Stelle
des Programmms ?
In der 1. Spalte
stehen die Adressen
der Variablen
...
In der 2. Spalte stehen
die Inhalte der
Adressen (Werte der
Variablen)
int main(){
Was veranlasst diese Anweisungen ?
int anz, zahl, i;
int *panf;
cout << "Anzahl eingeben:";
cin >> anz;
panf = new int[anz];
Auf die (Anfangs)Adresse
cout << "Zahlen eingeben:";
dieses Speicherbereichs hat
for(i=0; i<anz; i++){
der Programmierer keinen
cin >> zahl;
Einfluß. Diese legt der
*(panf+i)=zahl;
Compiler bzw.
}
Programmlader fest.
//...
panf 020
...
?
...
Es wird im Arbeitsspeicher
Platz für 2 integer-Zahlen
reserviert und die
(Anfangs)Adresse dieses
Speicherbereichs der Variable
panf zugewiesen.
int main(){
Annahme:
int anz, zahl, i;
Die
1. über
Tastatur eingegebene Zahl sei
int
*panf;
17cout
und die<<
2. eingegebene
Zahl sei 13.
"Anzahl eingeben:";
Was
bewirkt
dann jeweils (insgesamt
cin
>> anz;
zweimal)
Anweisung
?
panf =diese
new
int[anz];
cout << "Zahlen eingeben:";
for(i=0; i<anz; i++){
cin >> zahl;
*(panf+i)=zahl;
}
zeigt auf:
//...
panf 020
...
0700
...
0700
?
?
?
?
?
?
?
?
Speicherreservierung
für 2 integerZahlen,die z.B. bei der
Adresse 0700 beginnen
int main(){
int anz, zahl, i;
int *panf;
cout << "Anzahl eingeben:";
cin >> anz;
panf = new int[anz];
cout << "Zahlen eingeben:";
for(i=0; i<anz; i++){
cin >> zahl;
*(panf+i)=zahl;
}
//...
panf 020
...
0700
...
0700
0704
Man kann auch für Objekte
dynamisch Speicher
allokieren:
int main(){
new reserviert dynamisch
Speicher. Die Anfangsadresse
Konto *pk;
pk = new Konto; des dynamischen Speichers wird
in einem Zeiger festgehalten,
// Anweisungen damit man später darauf
delete pk;
zugreifen kann. Zusätzlich wird
automatisch der Konstruktor
}
aufgerufen.
Falls kein Speicher mehr allokiert werden kann, bekommt
pk den Wert NULL, den sogenannten Nullzeiger,
zugewiesen. Dieser Nullzeiger zeigt auf kein Objekt !
delete gibt den dynamisch erzeugten Speicher wieder frei.
Zusätzlich wird automatisch der Destruktor aufgerufen.
Falls pk gleich NULL ist, wird keine Operation ausgeführt.
In der schließenden Klammer } wird kein Destruktor aufgerufen,
da pk eine Zeigervariable ist.
Ein weiteres Beispiel:
Die Klasse Bank hat u.a. das
Attribut art (Datentyp char),
in dem gespeichert ist, ob ein
Konto einem Mitarbeiter
gehört (‘M‘), oder einer
fremden Person (‘F‘).
Die zugehörigen Methoden,
die diesen Modus setzen
bzw. lesen heissen (aus
Platzgründen verkürzt):
setM
getM
class Bank {
private:
char art;
...
public:
void setM(...)
...
In art wird die Art des
};
Mitarbeiters gespeichert,
also:
'M' oder 'F'
Annahme:
Ein Objekt der Klasse Bank
sei 100 Byte groß.
int main(){
Hier wird nachher jeweils der Anfang
des dynamischen Speichers (in einem
int anz;
Zeiger) festgehalten.
int *pi;
Konto *pk, *pkk;
Zufallszahl
anz = rand();
die folgenden 3 Befehle
pi = new int;
reservieren Speicher für:
pk = new Konto;
pkk = new Konto[anz];
...
einen integer-Wert
für ein Objekt der
Klasse Konto
für ein Feld von anz
Objekten der Klasse Konto
int main(){
Annahmen:
anz = 2
int anz;
Speicherplatz Konto: 100 Byte
int *pi;
Konto *pk, *pkk;
Welchen Wert haben
pi, pk, pkk
anz = rand();
an dieser Stelle des
pi = new int;
Programmms ?
pk = new Konto;
pkk = new Konto[anz]; In der 1. Spalte
pi 0100
...
pk 0120
...
pkk 0140
stehen die Adressen
der Variablen
...
...
In der 2. Spalte stehen
die Inhalte der
Adressen (Werte der
Variablen)
int main(){
Was veranlasst diese Anweisung ?
int anz;
Auf die (Anfangs)Adresse
int *pi;
reservierten Speichers hat
Konto *pk, *pkk; des
der Programmierer keinen
anz = rand();
Einfluß. Dies legt der
pi = new int;
Compiler (bzw.
pk = new Konto; Programmlader fest).
pkk = new Konto[anz];
pi 100
...
pk 120
...
pkk 140
?
...
?
...
?
Es wird im Arbeitsspeicher
Platz für eine integer-Zahl
reserviert und die
(Anfangs)Adresse dieses
Speicherbereichs der Variable
pi zugewiesen.
int main(){
0500
int anz;
int *pi;
Konto *pk, *pkk;
anz = rand();
zeigt auf:
pi = new int;
pk = new Konto;
pkk = new Konto[anz];
pi 0100
...
pk 0120
...
pkk 0140
0500
...
?
...
?
Speicherreservierung
für ein integer, das
z.B. bei der Adresse
0500 beginnt
?
?
?
?
int main(){
0500
int anz;
int *pi;
Konto *pk, *pkk;
anz = rand();
0504
pi = new int;
pk = new Konto;
pkk = new Konto[anz];
pi 0100
...
pk 0120
...
pkk 0140
0500
...
0504
...
?
zeigt auf:
?
?
?
?
?
...
...
?
Speicherreservierung
für ein Objekt der
Klasse Konto
(hier: 100 Byte)
int
main(){für
0504
Speicherreservierung
int
anz;
ein Feld
der Länge 2 mit
Objekten
der Klasse Konto
int
*pi;
(hier: 2*100
Byte) *pkk;
Konto
*pk,
anz = rand();
0604
pi = new int;
pk = new Konto;
pkk = new Konto[anz];
pi 0100
...
pk 0120
...
pkk 0140
0500
... zeigt auf: 0704
0504
...
0604
?
...
...
?
?
...
...
?
?
...
...
?
*pi = 5;
(*pk).setM('M');
// pk->setM('M');
(*(pkk+1)).setM('F');
// pkk[1].setM('F');
// (pkk+1)->setM('F');
delete pi;
delete pk;
delete [] pkk;
}
pi 0100
...
pk 0120
...
pkk 0140
0500
...
0504
...
0604
0504
0604
0704
?
...
...
?
?
...
...
?
?
...
...
?
*pi = 5;
0500
(*pk).setM('M');
// pk->setM('M');
(*(pkk+1)).setM('F');
// pkk[1].setM('F');
// (pkk+1)->setM('F');
delete
Bemerkung: pi;
Die Zahl (hier
5) wird in den zur Verfügung stehenden
delete
pk;
32 Bit gespeichert
welchen Bits die einzelnen Ziffern
delete
[] (in
pkk;
}der Zahl stehen, interessiert uns hier nicht).
pi 0100
...
pk 0120
...
pkk 0140
0500
...
0504
...
0604
gleiche Befehle, nur
anders geschrieben
*pi = 5;
(*pk).setM('M');
// pk->setM('M');
(*(pkk+1)).setM('F');
// pkk[1].setM('F');
// (pkk+1)->setM('F');
delete pi;
delete pk;
delete [] pkk;
}
pi 0100
...
pk 0120
...
pkk 0140
0500
...
0504
...
0604
0504
‘M‘
...
...
?
0604
?
...
...
?
?
0704
...
...
gleiche Befehle,
nur
anders geschrieben
?
*pi = 5;
(*pk).setM('M');
// pk->setM('M');
(*(pkk+1)).setM('F');
// pkk[1].setM('F');
// (pkk+1)->setM('F');
delete pi;
delete pk;
delete [] pkk;
}
pi 0100
...
pk 0120
...
pkk 0140
0500
...
0504
...
0604
0504
‘M‘
...
...
?
0604
?
0704
...
...
?
‘F‘
...
...
?
Was macht das gleiche
Programm, aber ohne die
folgenden Anweisungen:
pi = new int;
pk = new Konto;
pkk = new Konto[anz];
int main(){
Welchen Wert hat pi z.B.
an dieser Stelle des
int anz;
Programms ?
int *pi;
Konto *pk, *pkk;
anz = rand();
*pi = 5;
(*pk).setM('M');
// pk->setM('M');
(*(pkk+1)).setM('F');
// pkk[1].setM('F');
// (pkk+1)->setM('F');
...
int main(){
int anz;
int *pi;
Konto *pk, *pkk;
anz = rand();
*pi = 5;
(*pk).setM('M');
(*(pkk+1)).setM('F');
pi 0100
...
pk 0120
...
pkk 0140
?
...
...
...
...
? bedeutet:
irgendein unbekannter,
zufälliger Wert.
Zum Beispiel: 0815
int main(){
int anz;
int *pi;
Konto *pk, *pkk;
anz = rand();
Was steht an der Adresse
0815 im Arbeitsspeicher ?
Der Programmier weiß es
nicht, denn er hat an dieser
Adresse keinen
*pi = 5;
Speicherplatz reservieren
lassen und diesen
(*pk).setM('M');
(*(pkk+1)).setM('F');entsprechend belegt.
pi 0100
...
pk 0120
...
pkk 0140
0815
...
...
...
...
Im "schlimmsten" Fall
könnte an der Adresse
0815 zum Beispiel...
Ein Teil des
Betriebssystems beginnen !
int main(){
int anz;
int *pi;
Konto *pk, *pkk;
anz = rand();
*pi = 5;
(*pk).setM('M');
(*(pkk+1)).setM('F');
pi 0100
...
pk 0120
...
pkk 0140
0815
...
...
...
...
0815
Teil
des
Betr.
Syst.
int main(){
int anz;
int *pi;
Konto *pk, *pkk;
anz = rand();
0815
5
0
0
0
*pi = 5;
(*pk).setM('M');
(*(pkk+1)).setM('F');
pi 0100
...
pk 0120
...
pkk 0140
0815
...
...
...
...
Analoges gilt auch für die
folgenden Anweisungen
Dieser Teil des
Betriebssystems wird
überschrieben