§4 ZEIGER - ALLGEMEINES

mathematik.uni.muenchen.de

§4 ZEIGER - ALLGEMEINES

§4 ZEIGER - ALLGEMEINES

Leitideen: Zeiger sind Adressen mit zusätzlicher Typinformation

über das dort gespeicherte Objekt.

Diese Typinformation ermöglicht eine Zeigerarithmetik,

bei der die Addition von 1 die Adresse der

nächsten Komponente eines Vektors angibt.

Die Vereinbarungsyntax soll der Ausdruckssyntax

entsprechen und zugleich einfach interpretierbar

sein.

Zeigerparameter sind ein Ersatz für in C fehlende

Variablenparameter.

Bei Vektoren wird nur die Startadresse mit Typinformation

übergeben, das motiviert die in vielen

Fällen automatische Typumwandlung Vektor in

Zeiger.


§4 ZEIGER - THEMENÜBERSICHT

Motivation und Operatoren

Beispiele

Vereinbarungssyntax

Zeiger als Ersatz für Variablenparameter

Parameterübergabe mittels Stacks

Zeigerarithmetik

Unterschiede zwischen Vektoren und Zeigern

Parameterübergabe von Vektoren

Parameterübergabe von Matrizen


Eine einfache Idee . . .

Voraussetzungen

◮ Kleinste adressierbare Speichereinheit (Byte) als Datentyp

vorhanden, hier byte genannt. (Ganzzahltyp mit 8 Bit

Länge)

◮ Ganzzahldatentyp, der Adresswerte speichern kann:

hier long verwendet

Pseudocode (Kein C!)

byte b = 37;

printf("%ld\n",&b); // Adr. von b ausg., z.B. 2048

*(2048L) = 99 // Wert von b jetzt 99

Neue Operatoren

◮ & “Adressoperator“ und * “Inhaltsoperator“

◮ Es gilt: *&b = b und &*a = a, letzteres aber nur, wenn a

die Adresse eines Objekts (Variable) ist.


. . . und ihre Problematik

◮ Nur einzelne Bytes adressierbar, nicht längere Objekte.

◮ Z.B. bei Zahlen vom Typ int oder double müsste der

Programmierer die interne Darstellung kennen

◮ Die Abbildung Adresse → long ist maschinenabhängig.

Lösungsansatz in C

◮ Einführung von Datentypen (Zeigertypen), die Adressen

speichern können.

◮ Im jeweiligen Zeigerwert ist die Information enthalten, ob

es sich z.B. um die (Start)adresse eines double- oder

int-Objekts handelt.

◮ Der Adressoperator liefert Zeigerwerte (Adressen mit

Typinformation).

◮ Der Inhaltsoperator liefert zu Zeigerwerten das zugehörige

Objekt, das an dieser Adresse gespeichert ist.

◮ Variablen vom Zeigertyp sind möglich und gebräuchlich.

◮ Der Datentyp char übernimmt in C die Rolle von byte.


Zeiger - Überblick

Motivation

(Speicherplatz)adresse und Typ des dort gespeicherten

Datenobjekts (z.B. Wert einer Variablen) ermöglichen die

korrekte Interpretation der dort gespeicherten Bits.

Entsprechender Datentyp: Zeiger (auf Typ)

Bsp.: Die Adresse einer int-Variable wird mit Hilfe des

Datentyps “Zeiger auf int“ angegeben.

Operatoren

◮ Adressoperator &: Liefert Adresse+Datentyp (Zeigerwert)

eines Datenobjekts (z.B einer Variablen)

◮ Inhaltsoperator *: Liefert zur angegebenen Adresse das

dort gespeicherte Datenobjekt und interpretiert es gemäß

der Typinformation.

◮ Zeigerwert (Adresse) NULL (bzw. 0) bedeutet, dass dort

nichts gespeichert ist.

Die Anwendung des Inhaltsoperators darauf ist unzulässig.

Kann zu Programmabstürzen führen (segmentation fault)


Zeiger - Beispiel

◮ Oft werden Zeigervariablen mit einem p (“pointer“) am

Ende gekennzeichnet, etwa im folgenden Beispiel

ip und jp (Zeiger auf int)

Programm i j ip jp

int i=5,j,*ip,*jp=NULL; 5 undef undef NULL

ip=&i;

Adr. von i

j=*ip; 5

jp=ip;

Adr. von i

*jp=6; 6

Caveat

◮ Die Syntax für die Initialisierung und für die Zuweisung von

Zeigern ist unterschiedlich:

int *jp=NULL; wirkt wie int *jp; jp=NULL;

und nicht wie int *jp; *jp=NULL;


Zeiger - Veranschaulichung I

Variablenbelegung

i: 6

j:

5

ip:

Adr. von i

jp:

Adr. von i

Programmcode (ausgeführt)

int i,j,*ip,*jp;

i=5;

jp=NULL;

ip=&i;

j=*ip;

jp=ip;

*jp=6;


Zeiger - Veranschaulichung II

Variablenbelegung

i: 6 j:

5

ip

jp

Programmcode (ausgeführt)

int i,j,*ip,*jp;

i=5;

jp=NULL;

ip=&i;

j=*ip;

jp=ip;

*jp=6;


Zeiger - Vereinbarungssyntax

&i

*ip

int *ip

Adresse der Variable i

Inhalt des durch ip adressierten Speicherplatzes

ip Zeiger auf int,

d.h ip enthält Adresse eines int-Speicherplatzes

Ziel der Vereinbarungssyntax:

*ip

int *ip

int-Zahl

ip Zeiger auf int

Ähnlichkeit zu Ausdrücken

double *a[N] ?

double *(a[N]) ? (äquivalent zur vorigen Zeile!)

double (*b)[N] ?

Mechanismus:

double

} {{ }

3

double

} {{ }

3

}{{} ∗

2

( ∗

}{{}

1

Lesen von innen nach außen

(a [N] ) N-Vektor

}{{} } {{ }

1

1

b) [N]

}{{}

2

Zeiger

} {{ }

1

aus Zeigern auf

} {{ } }

double

{{ }

2

3

auf N-Vektor von double

} {{ }

2

} {{ }

3


Zeiger als Ersatz für Variablenparameter

◮ In C werden die eingesetzten Funktionsargumente bei der

Parameterübergabe kopiert (Wertparameterübergabe, call

by value).

◮ Eine Änderung dieser Werte in der Funktion bewirkt keine

Änderung evtl. eingesetzter Variablen in der aufrufenden

Funktion.

void vertausche(int i v , int j v )

// i v =i m ; j v =j m

{ int h v ;

h v =i v ; i v =j v ; j v =h v ;

return; }

int main(void)

{ int i m =2; j m =4;

vertausche(i m ,j m ); // i m ,j m

return 0;

}

unveraendert


Veranschaulichung

Variablen- und Parameterbelegung

main:

i:

2 j: 4

vertausche: i:

4 j: 2 h:

2

Programmcode (ausgeführt)

main:

vertausche: int h;

h=i;

i=j;

j=h;

int i,j;

i=2; j=4;

vertausche(i,j) // in Ausfuehrung


Zeiger als Ersatz für Variablenparameter II

◮ Die Effekte von Variablenparametern können mit Hilfe von

Zeigern erreicht werden.

void vertausche(int *ip v , int *jp v )

// ip v =&i m ; jp v =&j m ;

{ int h v ;

h v =*ip v ; // d.h. h v =i m

*ip v =*jp v ; // d.h. i m =j m

*jp v =h v ; // d.h. j m =h v

return;

}

int main(void)

{ int i m =2; j m =4;

vertausche(&i m ,&j m ); // bewirkt i m =4, j m =2

return 0;

}


Veranschaulichung

Variablen- und Parameterbelegung

main:

i:

4 j: 2

vertausche:

ip jp h: 2

Programmcode (ausgeführt)

main:

vertausche: int h;

h=*ip;

*ip=*jp

*jp=h;

int i,j;

i=2; j=4;

vertausche(&i,&j) // in Ausfuehrung


Zeiger als Ersatz für Variablenparameter III

Weiteres Beispiel

◮ Funktion verdopple soll Wert einer Variablen verdoppeln

und diesen Wert als Ergebnis liefern

int verdopple(int *ip v )

// ip v =&i m ; (s.u.)

{

*ip v = *ip v * 2; // d.h. i m =i m* 2

return *ip v // Rueckgabewert: i m (neu)

}

int main(void)

{

int i m =4, j m ;

j m = verdopple(&i m ); // i m =8, j m =8

return 0;

}


Stack für Variablen und Argumente

◮ Beim Aufruf einer Funktion werden u.a. lokale Variablen

und eingesetzte Argumente in einem neu reservierten

Speicherabschnitt (“Stack“) gespeichert.

◮ Nach Beendigung der Funktion wird der Stack wieder

freigegeben.

vor verdopple zu Beginn von verdopple

i m

m

: 4

j : −

im

:

j m :

ip :

v

4

Addresse

von im

main−Stack

verdopple−Stack

am Ende von verdopple

im

:

j m :

ip :

v

8

Addresse

von im

nach verdopple

im

: 8

j m : 8

main−Stack

verdopple−Stack


Zeiger als Ersatz für Variablenparameter IV

Funktion mit mehreren “Rückgabewerten“

Methoden:

1. Alle “Rückgabewerte“ mit Zeigern als Ersatz für

Variablenparameter übergeben.

Bsp. in der Standardbibliothek:

scanf

2. Einen “Rückgabewert“ über den Funktionswert, die

anderen mit Zeigern übergeben.

Bsp. in der Standardbibliothek: frexp, modf → Inf.bl 8/2

◮ Es gibt noch andere Möglichkeiten, z.B. Records und

Zeiger auf dynamisch reservierte Speicherbereiche als

Funktionswerte (später!)


Zeigerarithmetik

Motivation

◮ Zeiger p ist Adresse mit Typ ⇒ Adresse eines direkt

folgenden Objekts bestimmbar (Bez.: p + 1)

Bsp.: int *ip; // ip: 100 ⇒ ip+1: 104

(falls int 4 Byte)

◮ Adressarithmetik besonders wichtig bei Vektoren:

&a[i] := &a[0]+i ≡&(a[0])+i (aufeinanderfolgende Speicherung

der Komponenten)

a[i] ≡ ∗(&a[0]+i) (nur Adresse von Komp. 0 notwendig!)

◮ Automatische Umwandlung: Vektor → Zeiger (oft!)

Zeigerwert: Adresse der Komponente 0

[Wichtigste Ausnahme: Vektor steht links in Initialisierung

oder Zuweisung]

◮ Also: a[i] ≡ ∗(a+i)

◮ Allgemein: p[i] := ∗(p+i) . (Sogar: i[p]:=∗(i+p)≡ ∗(p+i)≡p[i])


Zeigerarithmetik II

Inkrement/Dekrementoperatoren

Increment- und Dekrementoperatoren sind auch auf Zeiger

anwendbar.

Beispiel: Vorbesetzen eines Felds mit a i = i

double a[N],*p;

p=a;

for (i=0;i


Unterschiede Vektor - Zeiger

Bsp.: double a[10],*p;

◮ Definition von a reserviert Speicherplatz für 10

double-Zahlen (hier: 640 Bit)

Definition von p reserviert keinen(!) Speicherplatz für eine

double-Zahl, sondern lediglich Speicherplatz für eine

Adresse (hier: 32 Bit bzw. 64 Bit)

◮ Die Umwandlung a→&a[0] liefert eine Zeigerkonstante,

keine Zeigervariable. Zuweisungen an a bzw. &a[0] sind

daher unzulässig. (Sonst Informationsverlust über die

Speicherlokalisation von a!).

p sei eine Zeigervariable.

a = p; // Unzulaessig: Vektor steht links

// vom Gleichheitszeichen

p = a; // Zulaessig: a -> &a[0], p: &a[0]

// Bem.: Es gilt p[i]==a[i] (i=0..9)


Beispiele zur Umwandlung Vektor→Zeiger

1. double a[N],b[N],*c;

a = b; // Unzulaessig: a Vektor links von =

a[i] = b[i]; // Zul. und aeq. zu *(a+i)=*(b+i)

c = b; // Zulaessig: b -> &b[0]

2. double b[N],*c;

c = &b // Nicht ganz korrekt (Compilerwarn.)

// Li: Zeiger auf double

// Re: Zeiger auf Vektor von double

3. double b[N],(*c)[N];

c = &b; // Korrekt:

// Li: Zeiger auf Vektor von double

// Re: Zeiger auf Vektor von double

4. double a[M][N],b[M][N];

a = b; // Unzulaessig: a Vektor links von =

a[i] = b[i]; // Unzul.: a[i] Vektor links von =

a[i][j] = b[i][j] // Zulaessig und aequiv. zu

// *(*(a+i)+j) = *(*(b+i)+j)


Veranschaulichung der Zeigerarithmetik bei Matrizen

double a[M][N]

a[i][j]

... ...

a

a[0]

a[1]

a+1 a+2

a+i

a[i]

a+M−1 a[M−1]

1. a+i ≡ &a[0]+i ≡ &a[i]

wegen a+i ≡&(∗(a+i)) ≡ &a[i]

Datentyp von a[0] bzw. a[i]: N-Vektor von double

2. a[i]+j ≡ &a[i][0]+j ≡ &a[i][j]

wegen a[i]+j ≡ &(∗(a[i]+j) ≡ &a[i][j]

Datentyp von a[0][0] bzw. a[i][0] bzw. a[i][j]:

Caveat

double

◮ Zwar sind die Adresswerte &a, &a[0] und &a[0][0] gleich ,

nicht aber die Datentypen!

Daher i.allg.: &a+1 ≠ &a[0]+1 ≠ &a[0][0]+1


Übergabe von Vektoren in Funktionen

Prinzip: Vektoren werden nicht kopiert, statt dessen wird

Adresse des Vektors (d.h. die Adresse der Komponente 0)

übergeben.

Problem: Die Länge des Vektors geht verloren.

Abhilfe: Länge ggf. zusätzlich als Argument übergeben.

◮ Eingesetzte Vektorargumente werden in Zeiger

umgewandelt.

(Spezialfall der bereits erwähnten Umwandlung

Vektor → Zeiger)

Bsp.: double a[N],b[N]; int n;

skalar(a,b,n) → skalar(&a[0],&b[0],n)

◮ Formale Vektorparameter werden in Zeiger umgewandelt.

Vektordimensionen können weggelassen werden!

Bsp.: double skalar(double a[N],double b[N],int n

≡ double skalar(double a[],double b[],int n)

≡ double skalar(double *a,double *b,int n)


Übergabe von Matrizen in Funktionen

Prinzip: Umwandlung Vektor → Zeiger findet nur einmal statt!

1. Methode

int lingl(double a[M][N],...,int m,int n)

≡ int lingl(double a[][N] ,...,int m,int n)

≡ int lingl(double (*a)[N],...,int m,int n)

Denn: double a[M][N] M-Vektor von N-Vektor von double

→ double (*a)[N] Zeiger auf N-Vektor von double



Grundsätzlich möglich, aber unpraktisch, weil bei

getrenntem Übersetzen von Haupt- und Unterprogramm

die Konstante N übereinstimmen müsste.

Wieder interessant in C 99, weil dort in beschränktem

Umfang variable Längen für Vektoren zulässig sind.


Übergabe von Matrizen in Funktionen II

2. Methode

int lingl(double *(ap[M]),...,int m,int n)

≡ int lingl(double *(ap[]) ,...,int m,int n)

≡ int lingl(double **ap ,...,int m,int n)

Denn: double *ap[M] M-Vektor von Zeigern auf double

→ double **ap Zeiger auf Zeiger von double



Zusätzlich im Hauptprogramm Hilfsvektor erforderlich:

double a[M][N], *ap[M]

for (i=0; i


Übergabe von Matrizen in Funktionen III

double a[M][N], *ap[M];

for(i=0; i

Weitere Magazine dieses Users
Ähnliche Magazine