¨Ubungspaket 20 Zeiger und Zeigervariablen

Werbung
Übungspaket 20
Zeiger und Zeigervariablen
Übungsziele:
1. Definition von Zeigervariablen
2. Verwendung von Zeigern
3. Arrays und Adressberechnungen
Skript:
Kapitel: 45 und 46
Semester:
Wintersemester 2017/18
Betreuer:
Kevin, Theo, Thomas und Ralf
Synopsis:
Zeiger! Bei diesem Wort bekommen viele Programmieranfänger Pusteln, Hautausschlag, hektische Flecken, Schweißausbrüche und dergleichen. Aber das muss nicht sein! Ein bisschen gezielt üben und schon
ist es kinderleicht. . . Na ja, vielleicht nicht ganz kinderleicht, aber ohne nennenswerte Hürde. In diesem Übungspaket schauen wir uns erst
einmal ganz einfach an, was Zeiger eigentlich sind und was wir mit
derartigen Variablen machen können. Dabei steht vor allem die Frage
nach dem Typ des Zeigers im Vordergrund. Zeiger und Funktionen heben wir uns für Übungspaket 21 auf. Am Ende der Übung 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: Außer ein paar Vereinfachungen bei Array-Zugriffen eigentlich
nicht viel. Die wirklich interessanten Dinge kommen erst in Übungspaket 21 und bei den dynamischen Datenstrukturen (Übungspakete 31
bis 33). Hierfür ist dieses Übungspaket eine sehr wichtige Vorbereitung.
Teil I: Stoffwiederholung
Aufgabe 1: Allgemeines zu Zeigern
Erkläre kurz, was ein Zeiger ist und was in einer Zeigervariablen abgespeichert wird.
Ein Zeiger ist nichts anderes als eine Adresse des Arbeitsspeichers. Implizit geht man
meist davon aus, dass sich dort auch tatsächlich etwas befindet. Ein Zeiger zeigt“ also
”
auf dieses Objekt. Entsprechend hat eine Zeigervariable eine Adresse als Inhalt.
Aufgabe 2: Zeiger-Syntax
Woran erkennt man eine Zeigerdefinition?
Am * vor dem Namen
Wie definiert man einen Zeiger?
Typ * Name
Wie weist man einem Zeiger etwas zu?
Name=Wert; //’Wert’ ist eine Adresse
Wie greift man auf den Zeigerinhalt zu?
Name //liefert die gespeicherte Adr.
Wie schreibt man etwas an die Stelle, auf
die der Zeiger zeigt?
Wie liest man von einer Stelle, auf die der
Zeiger zeigt?
Wie vergleicht man zwei Zeigervariablen
miteinander? (Beispiel)
Gibt es Werte, die eine Zeigervariable nicht
annehmen darf?
Gibt es Adressen, auf die man nicht zugreifen darf?
Darf die Zeigervariable dennoch diese Werte annehmen?
* Name = Wert
* Name
Name 1 != Name 2
Nein, alle Werte sind erlaubt
Ja, beispielsweise 0
int *p=0; /*ok*/ *p=... /*Absturz*/
Aufgabe 3: Typen von Zeigervariablen
Nehmen wir einmal an, eine Adresse des Arbeitsspeichers benötigt vier Bytes. Wie viele
Bytes werden dann bei jedem Zugriff auf eine Zeigervariable im Arbeitsspeicher gelesen?
4 (vier) (four) (quattro)
Gibt es Ausnahmen, bei der mehr oder weniger Bytes kopiert werden? Nein! (No) (non)
Einführung in die Praktische Informatik, Wintersemester 2017/18
20-1
Wenn es nun wirklich so ist, dass es von dieser Regel keine Ausnahme gibt, warum in
Gottes Namen legt dann das Lehrpersonal so viel Wert darauf, dass ein Zeiger auch einen
Typ haben muss? Erkläre dies in eigenen Worten:
Solange wir nur auf die Zeigervariablen zugreifen würden, wäre es in C tatsächlich egal,
was für einen Typ diese Zeiger haben. Bei einer vier-Byte Architektur würden dann
immer nur 32 Bit irgendwo hingeschrieben oder von irgendwo gelesen werden. Dies ändert
sich, sobald wir auf diejenige Speicheradresse zugreifen, auf die der Zeiger zeigt. Zeigt
beispielsweise ein Zeiger char *p auf ein Zeichen, so wird beim Zugriff *p = ’a’ ein
Byte kopiert. Bei einem int *p-Zeiger wären dies hingegen *p = 4711 vier Bytes. Aber
in beiden Fällen würden bei der Anweisung p = 0 vier Bytes an die Speicherstelle der
Zeigervariablen p kopiert werden. Um hier also korrekt arbeiten zu können, muss der
Compiler immer wissen, was sich am Ende des Zeigers befindet.
Aufgabe 4: Zeiger und Arbeitsspeicher
Vervollständige für das folgende Programm das skizzierte Speicherbildchen.
1
2
3
4
int i ;
int * p ;
p = & i;
* p = 4711;
Adresse
0xFFEE107C
0xFFEE1078
Variable
int i:
int *p:
Wert
4711
0xFFEE107C
Aufgabe 5: Zeiger, Arrays und Zeigeroperationen
Erkläre nochmals kurz, was ein Array ist und was der Name des Arrays symbolisiert:
Ein Array ist eine Zusammenfassung mehrerer Elemente des selben Typs, die alle nacheinander im Arbeitsspeicher abgelegt werden. Der Name des Arrays ist eine Konstante
und symbolisiert die Anfangsadresse des Arrays.
Zeiger, Arrays und int-Terme kann man eingeschränkt miteinander verknüpfen. Ergänze
folgende Tabelle, in der n ein int-Ausdruck, a ein Array und p und q beliebige Zeiger sind.
Ausdruck
Alternative
Effekt
& a[ n ]
p + n
a[ n ]
*(p + n)
p - n
p - q
a + n
& p[ n ]
*(a + n)
p[ n ]
& p[ -n ]
Bestimmt die Adresse des n-ten Elementes von a
p plus n Elemente weiter oben“
”
Inhalt des n-ten Elementes von a
Inhalt des n-ten Elementes von p
p plus n Elemente weiter unten“
”
Bestimmt die Zahl der Basiselemente zwischen p und q
20-2
Wintersemester 2017/18, Einführung in die Praktische Informatik
Teil II: Quiz
Aufgabe 1: Variablen und Zeiger
In dieser Quizaufgabe greifen wir nochmals das Miniprogramm der letzten Übungsaufgabe
des ersten Teils auf. Demnach haben wir folgende Situation vorliegen:
Adresse
0xFFEE107C
0xFFEE1078
1 int i = 4711;
2 int * p = & i ;
Variable
int i:
int *p:
Wert
4711
0xFFEE107C
Ferner gehen wir wieder davon aus, dass sowohl eine int-Variable als auch ein Zeiger genau
vier Bytes im Arbeitsspeicher belegen.
Vervollständige folgende Tabelle. Gehe dabei immer davon aus, dass am Anfang jedes
Ausdrucks alle Variablen wieder auf obige Initialwerte zurückgesetzt werden.
Ausdruck
Typ
Ergebnis
i
i += 1
i-p += 1
--p
p[ 0 ]
& i
& p
i > 0
p > 0
p > 10
p > p + 1
int
int
int
int
int
int
int
int
int
int
int
int
4711
4712
4711
0xFFEE1080
0xFFEE1078
4711
0xFFEE107C
0xFFEE1078
1
1
1
0
*
*
*
**
Kommentar
Dies ist einfach nur der Wert von i
Zuweisungen sind auch Ausdrücke
Post-Dekrement, Ausdruck: 4711, aber i = 4710
Ein Element ergibt hier vier Bytes
Pre-Dekrement, ein Element, vier Bytes
Zugriff auf das erste Array-Element
Adresse der Variablen i
Der Ort, an dem wir den Zeiger p finden
Logisch wahr ergibt den Wert 1
wie oben
wie oben
Logisch falsch ergibt 0
Einführung in die Praktische Informatik, Wintersemester 2017/18
20-3
Aufgabe 2: Zeichenketten und Zeiger
Diese Quizaufgabe ist ähnlich der vorherigen. Nur behandeln wir jetzt nicht normale“ Va”
riablen sondern Zeichenketten. Zugegebenermaßen macht dies die Sache etwas komplexer.
Wenn man sich aber langsam und Schritt für Schritt von innen nach außen vorarbeitet, ist
die Sache doch recht überschaubar. Im Übrigen gehen wir wieder davon aus, dass alle Zeiger
genau vier Bytes im Arbeitsspeicher belegen. Betrachten wir nun folgende Code-Zeilen:
1 char buf1 [] =
" Fruehling " ;
2 char * buf2
=
" Sommer " ;
3 char * buf3 [] = { " Herbst " , " Winter " };
Beschreibe kurz mit eigenen Worten, welchen Datentyp die drei Variablen haben:
buf1
Ein Array vom Typ char
buf2
Ein Zeiger auf ein char
buf3
Ein Array mit zwei Zeigern auf char
Vervollständige zunächst folgendes Speicherbildchen, denn es hilft drastisch beim Finden
der richtigen Lösungen :-)
Segment:
Adresse
0xFE34
0xFE30
0xFE2C
0xFE28
0xFE24
0xFE20
20-4
Stack
Variable
char *buf3[ 1 ]:
char *buf3[ 0 ]:
char *buf2
:
char
buf1[]
:
Wert
0xF840
0xF838
0xF830
’g’ ’\0’
’h’ ’l’ ’i’ ’n’
’F’ ’r’ ’u’ ’e’
Segment:
Adresse
0xF844
0xF840
0xF83C
0xF838
0xF834
0xF830
Konstanten
Wert
’e’ ’r’ ’\0’
’W’ ’i’ ’n’ ’t’
’s’ ’t’ ’\0’
’H’ ’e’ ’r’ ’b’
’e’ ’r’ ’\0’
’S’ ’o’ ’m’ ’m’
Wintersemester 2017/18, Einführung in die Praktische Informatik
Vervollständige nun folgende Tabelle:
Ausdruck
Typ
Ergebnis
buf1
char []
buf2
char *
buf3
char **
sizeof( buf1 ) int
sizeof( buf2 ) int
sizeof( buf3 ) int
buf1[ 3 ]
char
buf1 + 3
char *
0xFE20
0xF830
0xFE30
10
4
8
’e’
0xFE23
*(buf1 + 3)
buf2[ 3 ]
buf2 + 3
’e’
’m’
0xF833
char
char
char *
*(buf2 + 3)
char
buf3[ 0 ][ 2 ] char
buf3[ 1 ][ 0 ] char
& buf1
char **
& buf2
char **
& buf3
*(char *
[2])
& buf1[ 4 ]
char *
& buf2[ 4 ]
char *
& buf3[0][2]
char *
’m’
’r’
’W’
0xFE20
0xFE2C
0xFE30
Kommentar
Das komplette Array; Wert: Anfangsadresse
Eine ganz gewöhnliche Zeigervariable
Ein Array mit zwei char * Zeigern
Genau neun Nutzzeichen plus ein Nullbyte
Ein klassischer Zeiger
Ein Array bestehend aus zwei Zeigern
Genau eines der Zeichen, nämlich das vierte
Anfangsadresse plus drei Bytes, also die Adresse
von ’e’
Identisch mit buf1[ 3 ]
Das vierte Zeichen der Zeichenkette
Adresse in buf2 plus 3 Bytes, identisch mit &
buf2[ 3 ], also die Adresse des zweiten ’m’
Identisch mit buf2[ 3 ]
Das dritte Zeichen der ersten Zeichenkette
Das erste Zeichen der zweiten Zeichenkette
Der Ort an dem sich buf1 befindet
Der Ort an dem sich buf2 befindet
Der Ort an dem sich buf3 befindet
0xFE24 Adresse des 5. Zeichens (Stacksegment)
0xF834 Adresse des 5. Zeichens (Konstantensegment)
0xF83A Adresse des 3. Zeichens (Konstantensegment)
Einführung in die Praktische Informatik, Wintersemester 2017/18
20-5
Teil III: Fehlersuche
Aufgabe 1: Definition und Verwendung von Zeigern
Das folgende Programm enthält einige einfache Variablen und einige Zeiger. Der gewünschte Typ der Zeiger geht immer unmittelbar aus dem Namen hervor. Ferner gehen die
gewünschten Zeigeroperationen aus dem Kontext hervor. Ab Zeile 5 ist in jeder Zeile ein
Fehler vorhanden. Finde und korrigiere diese, damit Dr. A. Pointer ruhig schlafen kann.
1 # include < stdio .h >
2
3 int main ( int argc , char ** argv )
4
{
5
int i = 4711 , * i_ptr , * i_ptr_ptr *;
6
char c = ’x ’ , ** c_ptr ;
7
8
i_ptr = i ;
9
* i_ptr_ptr = & i_ptr ;
10
c_ptr = * c ;
11
12
printf ( " i =% d \ n " , i_ptr ) ;
13
printf ( " c = ’% c ’\ n " , c_ptr * ) ;
14
printf ( " i hat die Adresse
% p \ n " , * i_ptr ) ;
15
printf ( " c hat die Adresse
% p \ n " , & c_ptr ) ;
16
printf ( " i_ptr hat die Adresse % p \ n " , i_ptr ) ;
17
printf ( " i =% c \ n " , ** i_ptr_ptr ) ;
18
}
Zeile
Fehler
Erläuterung
Korrektur
5
*...*
Bei der Definition müssen die Sternchen für die Zeiger vor
dem Namen stehen.
**i ptr ptr
Da wir nur einen einfachen Zeiger haben wollen, darf vor
dem Namen auch nur ein * stehen.
*c ptr
Da wir die Adresse der Variablen i benötigen, muss vor
ihr der Adressoperator stehen.
& i
..............................................................................................................................................
6
* zu viel
..............................................................................................................................................
8
& fehlt
..............................................................................................................................................
9
* falsch
Der Stern vor dem Zeiger i ptr ptr ist in diesem Falle i ptr ptr
unsinnig, er muss weg.
..............................................................................................................................................
10
20-6
* statt & Hier benötigen wir wieder die Adresse einer Variablen also
ein & und nicht ein *.
& c
Wintersemester 2017/18, Einführung in die Praktische Informatik
Zeile
Fehler
Erläuterung
Korrektur
12
* fehlt
Da wir hier auf den Inhalt derjenigen Speicherstelle zu- * i ptr
greifen wollen, auf die i ptr zeigt, benötigen wir hier ein
* zum Dereferenzieren.
..............................................................................................................................................
13
* an der
falschen
Stelle
Der * zum Dereferenzieren muss vor dem Namen stehen
nicht dahinter.
* zu viel
Wir wollen die Adresse von i ausgeben, die bereits in i ptr
i ptr steht. Also dürfen wir nicht dereferenzieren, denn
dann würden wir den Inhalt von i ausgeben.
* c ptr
..............................................................................................................................................
14
..............................................................................................................................................
15
& zu viel
Hier gilt das gleiche wie ein Fehler weiter oben. Der Aus- c ptr
druck & c ptr würde uns fälschlicherweise die Adresse
der Variablen c ptr liefern.
..............................................................................................................................................
16 falsche
Variable
oder
& fehlt
Da wir die Adresse der Variablen i ptr ausgeben wollen, i ptr ptr
dürfen wir nicht den Inhalt dieses Zeigers ausgeben, son- oder
dern benötigen dessen Adresse. Die steht in i ptr ptr & i ptr
oder kann mittels & i ptr gebildet werden.
17 falsches
Format
Hier ist eigentlich alles richtig. Nur muss das Format auf
%d für Integer abgeändert werden.
..............................................................................................................................................
%d
Programm mit Korrekturen:
1 # include < stdio .h >
2
3 int main ( int argc , char ** argv )
4
{
5
int i = 4711 , * i_ptr , ** i_ptr_ptr ;
6
char c = ’x ’ , * c_ptr ;
7
8
i_ptr = & i ;
9
i_ptr_ptr = & i_ptr ;
10
c_ptr = & c ;
11
12
printf ( " i =% d \ n " , * i_ptr ) ;
13
printf ( " c = ’% c ’\ n " , * c_ptr ) ;
14
printf ( " i hat die Adresse
% p \ n " , i_ptr ) ;
15
printf ( " c hat die Adresse
% p \ n " , c_ptr ) ;
16
printf ( " i_ptr hat die Adresse % p \ n " , i_ptr_ptr ) ;
17
printf ( " i =% d \ n " , ** i_ptr_ptr ) ;
18
}
Einführung in die Praktische Informatik, Wintersemester 2017/18
20-7
Teil IV: Anwendungen
Aufgabe 1: Definition von Variablen und Zeigern
In dieser Aufgabe geht es lediglich darum, normale“ Variablen und Zeigervariablen zu
”
definieren. Vervollständige dafür die folgende Tabelle. Wir gehen wieder davon aus, dass
sowohl int-Variablen also auch Zeiger vier Bytes im Arbeitsspeicher belegen. Da die letzten
beiden Definitionen recht schwierig sind, sollten sie mit den Betreuern besprochen werden.
Was
C-Definition
sizeof(Variable)
Eine int-Variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Eine char-Variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ein Zeiger auf eine int-Variable . . . . . . . . . . . . . . . . . . . . . . . . . .
Ein Zeiger auf eine char-Variable . . . . . . . . . . . . . . . . . . . . . . . .
Ein Array mit drei int-Elementen . . . . . . . . . . . . . . . . . . . . . .
Ein Array mit drei int-Zeigern . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ein Zeiger auf ein Array mit drei int-Elementen
int i
char c
int *p
char *p
int a[ 3 ]
int *a[ 3 ]
int (*p)[ 3 ]
4
1
4
4
12
12
4
(ein Zeiger)
(drei ints)
(drei Zeiger)
(nur ein Zeiger)
Illustriere die letzten beiden Definitionen mittels eines kleinen Speicherbildchens und trage
in die jeweiligen Variablen Phantasiewerte ein.
Definition: int *a[3]
Adresse
0xFFEE1028
0xFFEE1024
0xFFEE1020
Variable
Wert
a[ 2 ] : 0xFFFF3034
a[ 1 ] : 0xFFFF44CC
a a[ 0 ] : 0xFFFF2D14
Anmerkung:
Das Array a hat genau drei Elemente. Jedes dieser Elemente ist ein Zeiger, der seinerseits auf ein int zeigt.
Definition: int (*p)[3]
Adresse
0xFFEE1028
0xFFEE1024
0xFFEE1020
Variable
[ 2 ] :
[ 1 ] :
[ 0 ] :
0xFFEE0300
p
20-8
Wert
-1
815
4711
: 0xFFEE1020
Anmerkung:
Die Variable p ist ein Zeiger auf ein
Array. Dort befinden sich dann drei
int-Werte. Bei diesem Array kann
es sich um ein gewöhnliches“ oder
”
ein dynamisch angelegtes Array (siehe auch Übungspaket 29) handeln.
Wintersemester 2017/18, Einführung in die Praktische Informatik
Aufgabe 2: Zeiger auf Zeiger
Zeiger auf Zeiger, langsam wird’s ernst. Nehmen wir diesmal den Basistyp double. Aus
irgendeinem unerfindlichen Grund brauchen wir hiervon eine einfache Variable. Nennen wir
sie d wie double. Ferner brauchen wir noch einen Zeiger p auf diese Variable, einen Zeiger pp,
der auf diesen Zeiger zeigt und schließlich einen Zeiger ppp der auf letzteren zeigt. Definiere
die entsprechenden Variablen und vervollstängige unten stehendes Speicherbildchen. Bei
Schwierigkeiten stehen die Betreuer für Diskussionen zur Verfügung.
1
2
3
4
5
6
7
8
9
double
double
double
double
d
p
pp
ppp
=
=
=
=
d;
*p;
** pp ;
*** ppp ;
3.141;
& d;
& p;
& pp ;
Adresse
0xFFEE100C
0xFFEE1008
//
//
//
//
//
//
//
//
the single variable
a pointer to a double
a pointer to a double pointer
a pointer to a double ptr . ptr .
giving it a well - known value
p now points to d ;
& d -> double *
pp now points to p ;
& p -> double **
ppp now points to pp ; & pp -> double ***
Variable
Wert
ppp
: 0xFFEE1008
pp
: 0xFFEE1004
Adresse
0xFFEE1004
0xFFEE1000
Variable
Wert
p
: 0xFFEE1000
d
:
3.141
Vervollständige die folgenden Ausdrücke unter der Annahme, dass ein double acht und ein
Zeiger vier Bytes im Arbeitsspeicher belegt.
Ausdruck
d
p
*p
pp
*pp
**pp
ppp
*ppp
**ppp
***ppp
Typ
double
double
double
double
double
double
double
double
double
double
Ergebnis
sizeof(Ausdruck)
.....
* ...
.....
** .
* ...
.....
***
** .
* ...
.....
8
............................ 4
............................ 8
............................ 4
............................ 4
............................ 8
............................ 4
............................ 4
............................ 4
............................ 8
............................
3.141
. 0xFFEE1000
. . . . . . . . . . . 3.141
. 0xFFEE1004
. 0xFFEE1000
. . . . . . . . . . . 3.141
. 0xFFEE1008
. 0xFFEE1004
. 0xFFEE1000
. . . . . . . . . . . 3.141
...........
Zeige alle vier Möglichkeiten, dem Speicherplatz der Variablen d eine 2.718 zuzuweisen.
1. d = 2.718
2. *p = 2.718
3. **pp = 2.718
Einführung in die Praktische Informatik, Wintersemester 2017/18
4. ***ppp = 2.718
20-9
Aufgabe 3: Verwendung von Zeigern
1. Aufgabenstellung
Definiere ein Array einer beliebigen Größe, eines beliebigen Typs. Schreibe zwei Programme. Das erste Programm soll alle Elemente dieses Arrays mittels einer forSchleife und einer Indexvariablen initialisieren. Schreibe ein zweites Programm, dass
diese Initialisierung mittels einer for-Schleife und Zeigern (also nicht mit einer Indexvariablen) durchführt.
2. Pflichtenheft
Aufgabe
: Entwicklung zweier Programme, die ein Array mittels einer Indexvariablen bzw. mittels zweier Zeiger initialisieren.
Eingabe : keine
Ausgabe : keine
Sonderfälle : keine
3. Kodierung
Lösung mittels Indexvariable:
1 # define SIZE
10
2 # define INIT
0
3
4 int main ( int argc , char ** argv )
5
{
6
int arr [ SIZE ];
7
int i ;
8
for ( i = 0; i < SIZE ; i ++ )
9
arr [ i ] = INIT ;
10
}
Lösung mittels Zeigern:
1 # define SIZE
10
2 # define INIT
0
3
4 int main ( int argc , char ** argv )
5
{
6
int arr [ SIZE ];
7
int * p ;
8
for ( p = arr ; p < arr + SIZE ; p ++ )
9
* p = INIT ;
10
}
20-10
Wintersemester 2017/18, Einführung in die Praktische Informatik
Herunterladen