Definition und einfache Anwendungen von C-Zeigern

Zeiger sind Variable, deren Wert eine Adresse ist. Man kann sie mit der Adresse einer anderen Variable initialisieren. Da unterschiedliche Datentypen unterschiedlich gro├čen Speicherplatz belegen, muss bei der Deklaration eines Zeigers angegeben werden, welchen Datentyp die Variable besitzt, auf deren Speicherplatz er verweist. Diese Eigenschaften von Zeigern und mit welchen Operatoren (Adressoperator, Indirektionsoperator) dies realisiert wird, wird hier ausf├╝hrlich diskutiert. Als Anwendung wird gezeigt, wie man mit Zeigern Funktionen realisieren kann, die mehrere R├╝ckgabewerte besitzen.
Noch keine Stimmen abgegeben
Noch keine Kommentare

Einordnung des Artikels

Einfache Aufgaben zu den hier behandelten Konzepten finden sich in Programmier-Aufgaben in C: erste Schritte mit Feldern und Zeigern.

Begriffe im Zusammenhang mit Zeigern

Die folgende Tabelle gibt einen kurzen ├ťberblick ├╝ber die Begriffe, die mit dem Zeiger-Konzept von C verbunden sind und die in den folgenden Abschnitten erkl├Ąrt werden.

Variable Bei der Deklaration einer Variable wird gem├Ą├č ihres Datentyps ein geeigneter Speicherplatz reserviert. ├ťber den Namen der Variable kann man den Speicherplatz jederzeit ansprechen.
Referenz auf eine Variable Referenzen gibt es erst in C++, aber noch nicht in C. Mit Referenzen kann man in C++ unter einem anderen Namen auf eine Variable zugreifen; in C muss dies mit Zeigern realisiert werden.
Zeiger (pointer) Ein Zeiger ist eine Variable, die als Wert die Speicher-Adresse einer anderen Variable besitzt; der Zeiger erlaubt somit den Zugriff auf diejenige Variable, "auf die er zeigt".
Adressoperator & Mit dem Adressoperator kann auf die Speicher-Adresse einer Variable zugegriffen werden.
Dereferenzierung eines Zeigers Zeigt px auf die Variable x , so kann mit * px auf den Wert von x zugegriffen werden.
Indirektionsoperator (oder Inhaltsoperator) Der Operator * zur Dereferenzierung eines Zeigers wird als Indirektionsoperator bezeichnet. Er ist wie der Adressoperator un├Ąr, was bedeutet, dass er auf ein Objekt angewendet wird. (Zum Vergleich: Die Addition + ist ein bin├Ąrer Operator.)

Um mit der Syntax f├╝r Zeiger und verwandten Konzepten vertraut zu werden, soll gezeigt werden, wie sie im Quelltext eingesetzt werden:

// Deklaration einer Variable:
int x;

// Deklaration einer Referenz auf eine Variable (nicht in C):
int & y;

// Deklaration eines Zeigers:
int * ptr_x;

// Initialisierung eines Zeigers mit der Adresse einer Variable:
ptr_x = &x;

// Dereferenzierung eines Zeigers (Zugriff auf den Wert der Variable, auf die er zeigt):
* ptr_x

Einfache Anwendungen eines Zeigers

Das folgende Listing zeigt die einfachen Anwendungen, die man jetzt mit Zeigern ausf├╝hren kann. Es soll ausdr├╝cklich betont werden, dass Zeiger nicht f├╝r derartige Operationen eingef├╝hrt wurden ÔÇô die relevanten Anwendungen von Zeigern k├Ânnen erst im Zusammenhang mit Feldern und Funktionen gezeigt werden.

int x = 5;
printf("x = %i\n", x);          // x = 5

int * p;        // Deklaration eines Zeigers
p = &x;         // Initialisierung des Zeigers p

// Dereferenzierung des Zeigers p:
int y = *p;
printf("y = %i\n", y);          // y = 5

*p = 17;
printf("x = %i\n", x);          // x = 17
printf("y = %i\n", y);          // y = 5

Zeile 1 und 2: Eine int-Variable wird mit 5 initialisiert und sofort ausgegeben.

Zeile 4 und 5: Ein Zeiger auf eine int-Variable wird zuerst deklariert und dann mit der Adresse von x initialisiert.

Zeile 8 und 9: Eine weitere int-Variable y wird mit *p initialisiert. So wie bisher Variablen initialisiert wurden, ist die Zuweisung in Zeile 8 nicht verst├Ąndlich, da der Ausdruck auf der rechten Seite mit einem Zeiger arbeitet. Durch die Ausgabe in Zeile 9 erkennt man, dass y mit dem Wert von x initialisiert wurde, wozu der Zeiger p verwendet wurde.

Der un├Ąre Operator * in Zeile 9 wird als Indirektionsoperator (oder Inhaltsoperator) bezeichnet. Wird der Operator * auf den Zeiger p angewendet, greift man auf den Wert der Variable x zu, auf die der Zeiger p zeigt (siehe Initialisierung von p in Zeile 5).

Zeile 11 bis 13: Der Zugriff auf einen Zeiger mit dem Indirektionsoperator * kann nicht nur auf der rechten Seite einer Zuweisung erfolgen: Wie in Zeile 11 kann man der Variable, die sich hinter p verbirgt, einen Wert zuweisen, indem man *p setzt. Dadurch wird x neu gesetzt (siehe Zeile 12). Die Variable y wird nicht ver├Ąndert. Man beachte den Unterschied: y wurde initialisiert mit Hilfe des Zeigers p, aber p ist ein Zeiger auf die Variable x; Zeile 11 wirkt nur auf x, nicht auf y.

Kennt man den Indirektionsoperator * , kann man die Deklaration des Zeigers p, also int * p; auf zwei Arten lesen ÔÇô die Klammern sollen die Lesarten verdeutlichen, sie sind in der Syntax von C nicht vorgesehen:

int * p;        // Deklaration eines Zeigers

// 1. Lesart:
(int *) p       // "p ist vom Datentyp Zeiger auf eine int-Variable"

// 2. Lesart:
int (*p);       //"die Gr├Â├če *p kann wie eine int-Variable eingesetzt werden"
  1. Lesart: p ist vom Datentyp "Zeiger auf eine int-Variable". Hier wird also * auf den Datentyp int bezogen.
  2. Lesart: Die Gr├Â├če * p , also die Anwendung des Indirektionsoperators auf einen Zeiger, kann wie eine int-Variable eingesetzt werden. (Betrachtet man die Anweisungen aus dem Listing oben, in denen * p vorkommt, so sind alle syntaktisch korrekt, wenn man anstelle von * p eine int-Variable verwendet.)

Zeiger und Speicherzugriff

Wie man Variablen einsetzt, muss hier nicht mehr erkl├Ąrt werden, es soll lediglich aus einer anderen Perspektive beleuchtet werden ÔÇô und soll dann zu einem besseren Verst├Ąndnis von Zeigern beitragen:

int x = 5;
int y = x + 17;

Das letzte Listing definiert zuerst eine int-Variable x, die mit dem Wert 5 belegt wird. Anschlie├čend wird eine weitere int-Variable y deklariert, die mit Hilfe von x initialisiert wird; der Wert von y muss jetzt 22 betragen.

Die Verwendung von Variablen soll dem Programmierer die Arbeit mit ver├Ąnderlichen Gr├Â├čen erleichtern ÔÇô Wertzuweisungen und andere Operationen werden ganz ├Ąhnlich wie in der Mathematik geschrieben (immer unter em Vorbehalt, dass das Gleichheitszeichen eine Zuweisung vornimmt und vom Vergleichs-Operator == unterschieden werden muss).

Intern verbirgt sich hinter einer Variable die Adresse einer Speicherzelle; die Gr├Â├če der Speicherzelle wird durch den Datentyp der Variable vorgegeben. Eine Zuweisung wie x = 5 greift also auf die Speicherzelle zu, deren Adresse an die Variable x gekoppelt ist. Entsprechend wird bei der Zuweisung y = x + 17 zuerst der Wert aus der "Speicherzelle x" geholt, die Zahl 17 addiert und anschlie├čend das Ergebnis in der "Speicherzelle y" abgelegt.

Abbildung 1 soll dies veranschaulichen. Dazu wird der Speicher in zwei Teile zerlegt: jeweils links stehen die Adressen und rechts die Inhalte der Speicherzellen. Die Gr├Â├čen der Speicherzellen (also wie viele Bytes f├╝r eine Variable reserviert werden m├╝ssen) und die Adressen sind willk├╝rlich gew├Ąhlt.

Abbildung 1: Die Deklaration und Initialisierung von Variablen wird intern ├╝ber Adressen von Speicherzellen organisiert. Bei der Deklaration einer Variable wird ein geeigneter Speicherplatz reserviert; bei der Initialisierung wird der Speicherplatz mit Inhalt gef├╝llt. Im Programm greift man auf den Speicherplatz ├╝ber die Variablen und nicht ├╝ber die Adressen zu.Abbildung 1: Die Deklaration und Initialisierung von Variablen wird intern ├╝ber Adressen von Speicherzellen organisiert. Bei der Deklaration einer Variable wird ein geeigneter Speicherplatz reserviert; bei der Initialisierung wird der Speicherplatz mit Inhalt gef├╝llt. Im Programm greift man auf den Speicherplatz ├╝ber die Variablen und nicht ├╝ber die Adressen zu.

In den meisten Programmiersprachen kann man nicht ÔÇô oder nur sehr schwer ÔÇô nachvollziehen, welche Adressen die Variablen besitzen und man kann auf die Speicherzellen nur ├╝ber die Variablen, aber nicht ├╝ber die Adressen zugreifen.

Das Zeiger-Konzept von C erlaubt diesen direkten Zugriff auf den Speicher ├╝ber die Adressen der Speicherzellen.

Abbildung 2 soll dies veranschaulichen: Zur Variable x aus Abbildung 1 wird jetzt ein Zeiger px definiert, der mit der Adresse & x der Variable x initialisiert wird. Der Zugriff auf den Inhalt von x kann jetzt

  • wie gewohnt ├╝ber die Variable x erfolgen, oder
  • ├╝ber den Zeiger px.

Der Zeiger kann verwendet werden:

  • um den Wert von x zu lesen oder
  • um den Wert von x zu ver├Ąndern.

Abbildung 2: Darstellung der Operationen mit einem Zeiger px auf die Variable x. Links: Deklaration des Zeigers und Initialisierung mit der Adresse von x. Mitte: Zugriff auf den Wert von x mit Hilfe des Zeigers px und des Indirektionsoperators; der so gewonnene Wert wird einer neuen Variable y zugewiesen. Rechts: Der Indirektionsoperator kann auch eingesetzt werden, um der Variable x einen neuen Wert zuzuweisen. Der Wert von y bleibt dadurch unver├Ąndert, da px ein Zeiger auf x ist (und nicht auf y).Abbildung 2: Darstellung der Operationen mit einem Zeiger px auf die Variable x. Links: Deklaration des Zeigers und Initialisierung mit der Adresse von x. Mitte: Zugriff auf den Wert von x mit Hilfe des Zeigers px und des Indirektionsoperators; der so gewonnene Wert wird einer neuen Variable y zugewiesen. Rechts: Der Indirektionsoperator kann auch eingesetzt werden, um der Variable x einen neuen Wert zuzuweisen. Der Wert von y bleibt dadurch unver├Ąndert, da px ein Zeiger auf x ist (und nicht auf y).

Weitere Eigenschaften von Zeigern

Ein Zeiger ist an einen Datentyp gebunden, nicht an eine Variable

So wie in obigen Beispielen Zeiger eingesetzt wurde, kann der Eindruck entstehen, dass ein Zeiger an eine Variable gebunden ist. Dies ist falsch. Die Deklaration eines Zeigers legt nur fest, von welchem Datentyp die Variable sein muss, auf die er zeigt. Man kann ihn jederzeit auf eine andere Variable zeigen lassen. Man sollte dies aber nur mit Bedacht einsetzen: Zeiger k├Ânnen Quelltexte schwerer verst├Ąndlich machen, wechselnde Zeiger noch viel mehr.

Das folgende Listing zeigt die Bindung eines Zeigers an einen Datentyp:

int x = 5;
int * p = &x;

printf("x = %i\n", *p);         // x = 5

int y = 17;
p = &y;

printf("y = %i\n", *p);         // y = 17

double z = 1.5;
// p = &z;          // Syntax-Fehler: Deklaration von p beachten!

Der Zeiger p wird zuerst mit der Adresse von x initialisiert und eingesetzt, um auf den Wert von x zuzugreifen (Zeile 4).

Sp├Ąter wird p die Adresse von y zugewiesen; jetzt kann auf den Wert von y zugegriffen werden (Zeile 9).

Die Adresse der double-Variable z kann p nicht zugewiesen werden (Zeile 12).

Ein Zeiger kann wie eine Variable eingesetzt werden

Ein Zeiger kann als Spezialfall einer Variable aufgefasst werden; speziell in dem Sinn, dass sein Inhalt eine Adresse ist, ├╝ber die man auf eine andere Variable zugreifen kann. Aber wenn ein Zeiger eine Variable ist, kann man Zuweisungen vornehmen und der Zeiger darf darin sowohl auf der rechten als auch auf der linken stehen. Das folgende Listing zeigt einige Beispiele:

int x = 5;
int y = 17;

int * px = & x;
int * py = & y;

printf("Dereferenzierung von px und py: %d || %d \n", * px, * py);
// Dereferenzierung von px und py: 5 || 17 

py = px;

printf("Dereferenzierung von px und py: %d || %d \n", * px, * py);
// Dereferenzierung von px und py: 5 || 5

* py = 42;

printf("x, y: %d %d \n", x, y);
// x, y: 42 || 17

Zeile 1 bis 8: Hier werden nur Variablen und Zeiger definiert und ├╝ber die Zeiger wird auf die Werte der Variablen zugegriffen.

Zeile 10 weist dem Zeiger py den Zeiger px zu. An der folgenden Ausgabe (Zeile 12 und 13) sieht man, dass man jetzt mit py auf den Wert von x zugreift.

Zeile 15: Da py jetzt auf x verweist, wird in der Zuweisung der Wert von x ver├Ąndert. Die folgende Ausgabe zeigt, dass y bei allen Operationen unver├Ąndert geblieben ist (Zeile 17 und 18).

Der NULL-Pointer

F├╝r "herk├Âmmliche" Variablen wurde schon mehrfach gesagt, dass man sie nach der Deklaration sofort initialisieren sollte. Nicht-initialisierte Variablen k├Ânnen unbestimmt sein. Dies gilt ebenso f├╝r Zeiger.

Wird ein Zeiger erst deklariert und sp├Ąter initialisiert, so sollte man ihn ÔÇô zur Sicherheit ÔÇô mit dem NULL-Pointer (oder Nullzeiger) initialisieren.

Das folgende Skript zeigt zuerst die Verwendung eines nicht-initialisierten Zeigers und dann die Initialisierung mit dem Nullzeiger:

int * p;

printf("Nicht initialisierter Zeiger: %p \n", p);           // Compiler-Warnung
// Nicht initialisierter Zeiger: 00401B8B 

printf("Worauf verweist der nicht initialisierte Zeiger: %d \n", * p);
// Worauf verweist der nicht initialisierte Zeiger: 1528349827 

int * q = NULL;
int x = 5;

if (q != NULL) {
    printf("if-Zweig: Worauf verweist q? %d \n", * q);
} else {
    q = & x;
    printf("else-Zweig: Worauf verweist q? %d \n", * q);
}
// else-Zweig: Worauf verweist q? 5

Zeile 3 Die Verwendung des nicht-initialisierten Zeigers erzeugt eine Compiler-Warnung.

Zeile 6: Der nicht-initialisierte Zeiger l├Ąsst sich auch dereferenzieren ÔÇô das Verhalten ist aber unbestimmt.

Zeile 9: Um dieses Verhalten zu vermeiden, sollte man Zeiger, die nicht sofort initialisiert werden, vor├╝bergehend mit NULL initialisieren. Dabei handelt es sich um einen vordefinierten Zeiger, der auf die Speicher-Adresse 0 zeigt ÔÇô mit seiner Verwendung kann man keinen Schaden anrichten, da man auf diese Adresse niemals zugreifen darf.

Wie man am Verhalten des nicht-initialisierten Zeigers p sieht, l├Ąsst er sich nicht von einem initialisierten Zeiger unterscheiden (bis auf die Compiler-Warnung, die man leicht ├╝bersehen kann).

Zur Sicherheit kann man Zeiger vor ihrer Verwendung auf NULL pr├╝fen, siehe Zeile 12.

Die Funktion swap(): Vertauschen der Werte zweier Variabler

Im Abschnitt Funktionsaufruf mit call by reference in Spezielle Konzepte der strukturierten Programmierung in C++: call by reference, Rekursion, Function Templates wurde diskutiert, wie die Funktion swap() zum Vertauschen der Werte zweier Variabler in C++ realisiert wird.

In C gibt es keine Referenzen, daher muss die Funktion swap() hier mit Zeigern implementiert werden. Das folgende Listing zeigt:

  • Die Deklaration der Function swap() (Zeile 4).
  • Den Aufruf von swap() innerhalb der main()-Funktion in Zeile 15 (die main()-Funktion wird hier also ausdr├╝cklich gezeigt).
  • Eine m├Âgliche Implementierung der Function swap() (Zeile 27 bis 31).
#include <stdio.h>
#include <stdlib.h>

void swap(int * pa, int * pb);

int main(void) {

    int x = 5;
    int y = 17;

    // Adressen von x und y:
    printf("& x: %p || & y: %p \n", & x, & y);
    // & x: 0061FF1C || & y: 0061FF18

    swap(& x, & y);

    printf("x = %d ; y = %d\n", x, y);
    // x = 17 ; y = 5

    // Adressen von x und y:
    printf("& x: %p || & y: %p \n", & x, & y);
    // & x: 0061FF1C || & y: 0061FF18

    return 0;
}

void swap(int * pa, int * pb){
    int tmp = * pa;
    * pa = * pb;
    * pb = tmp;
}

Zeile 4: Die Deklaration der Funktion swap() zum Vertauschen der Werte zweier ganzer Zahlen. Man beachte, dass die Eingabewerte jetzt Zeiger auf integer sind; in C++ stehen hier Referenzen.

Zeile 6 bis 25: In der main()-Funktion wird swap() getestet.

Zeile 8 und 9: Es werden zwei int-Variablen x und y deklariert und sofort initialisiert.

Zeile 12 und 13: Die Ausgabe zeigt die Adressen von x und y. Diese Ausgabe ist nat├╝rlich nicht reproduzierbar, da man auf die Vergabe der Adressen bei der ├╝blichen Definition von Variablen keinen Einfluss nehmen kann. Mit Hilfe des Adressoperators wird auf die Adressen von x und y zugegriffen; dies sind die Anweisungen & x und & y innerhalb von printf().

Zeile 15: Beim Aufruf der Funktion swap() werden ebenfalls die Adressen von x und y ├╝bergeben.

Zeile 17 bis 22: Die erneute Ausgabe der Werte und Adressen zeigt, dass die Werte tats├Ąchlich vertauscht wurden, den Variablen aber noch ihre urspr├╝nglichen Adressen zugewiesen sind.

Zeile 27 bis 31: Die Implementierung von swap().

Zeile 27 wiederholt die Deklaration von swap().

Zeile 28 initialisiert eine int-Variable, in der der Wert der ersten Variable aus dem Funktionsaufruf von swap() abgespeichert wird. Man beachte den Unterschied zwischen der linken und der rechten Seite. Links wird wie ├╝blich eine int-Variable deklariert (hier steht kein * ). Rechts wird mit Hilfe des Indirektionsoperators* auf den Wert der Variable zugegriffen, die sich hinter dem Zeiger pa verbirgt. Dies ist das erste Argument von swap() und im Beispiel aus der main()-Funktion erh├Ąlt man hier den Wert von x, also 5.

Zeile 29: Man beachte wieder den Unterschied zwischen der linken und der rechten Seite. Links steht * pa , somit wird der Wert der Variable neu gesetzt, auf die der Zeiger pa verweist. Im Beispiel wird also der Wert von x neu gesetzt. Rechts findet dagegen der lesende Zugriff auf eine Variable statt; es wird der Wert derjenigen Variable gelesen, auf die der Zeiger pb verweist. Im Beispiel ist dies y. Somit wird in die Speicherzelle von x der Wert von y geschrieben.

Zeile 30: Und ganz ├Ąhnlich wird der in tmp zwischengespeicherte Wert der Variable zugewiesen, auf die pb verweist. Im Beispiel wird also der Wert von x (der in tmp gespeichert wurde) in die Speicherzelle von y eingetragen.

Man beachte weiter, dass die Funktion swap() keinen R├╝ckgabewert besitzt; der Zugriff auf x und y erfolgt ├╝ber die Zeiger und muss nicht in einem R├╝ckgabewert an das Hauptprogramm ├╝bertragen werden.

Aufgaben:

1. Lesen Sie in der C++-Dokumentation nach, wie dort swap() deklariert ist und welche Eigenschaften sie hat; sie befindet sich in der Algorithm library.

Formulieren Sie die Unterschiede zwischen der C++ Version von swap() und der oben implementierten Funktion.

Hinweis: In C gibt es keine Function Templates.

2. Veranschaulichen Sie die den Aufruf der Funktion swap() ├Ąhnlich wie in Abbildung 2.

Funktionen mit mehreren R├╝ckgabewerten: Realisierung durch ├ťbergabe von Zeigern

Die Funktion swap() aus dem letzten Abschnitt mag auf den ersten Blick wie eine kleine Spielerei wirken ÔÇô die Werte von zwei Variablen kann man auch vertauschen, indem man direkt mit der tempor├Ąren Variable arbeitet. Bei n├Ąherer Betrachtung er├Âffnet eine Funktion eine M├Âglichkeit, "Funktionen mit mehreren R├╝ckgabewerten" zu implementieren.

Warum hier Funktionen mit mehreren R├╝ckgabewerten in Anf├╝hrungstrichen geschrieben wird, muss n├Ąher erkl├Ąrt werden. In C muss bei der Deklaration einer Funktion ein Datentyp f├╝r den R├╝ckgabewert angegeben werden. Dieser kann entweder void sein (kein R├╝ckgabewert) oder ein fundamentaler Datentyp (wie int, double und so weiter). Felder, also eine gewisse Anzahl von Elementen, die alle denselben fundamentalen Datentyp besitzen, sind dagegen in C nicht erlaubt. Erst mit dem Konzept der Struktur (struct) lassen sich mehrere R├╝ckgabewerte zu einem Objekt zusammenfassen.

M├Âchte man auf Strukturen verzichten, kann man die R├╝ckgabe mehrerer Werte durch Zeiger realisieren:

  • Man deklariert zuerst die gew├╝nschten Variablen (mit einem geeigneten Datentyp),
  • ├╝bergibt ihre Adressen an eine Funktion und
  • l├Ąsst von der Funktion die Werte der Variablen berechnen.

Das folgende Beispiel soll dies demonstrieren; zu einem gegebenen x-Wert werden die zweite, dritte und vierte Potenz von x in einer Funktion berechnet. Die Potenzen von x werden als Adressen ├╝bergeben.

Wer schon mit Feldern vertraut ist, wird f├╝r dieses Beispiel eine deutlich einfachere Version angeben k├Ânnen.

#include <stdio.h>
#include <stdlib.h>

void powers(double x, double * x2, double * x3, double * x4);

int main(void) {

    double x, x2, x3, x4;
    x = 2.5;
    powers(x, & x2, & x3, & x4);
    printf("Potenzen von %1.2f : %1.2f, %2.3f, %2.4f", x, x2, x3, x4);
    // Potenzen von 2.50 : 6.25, 15.625, 39.0625
    return EXIT_SUCCESS;
}

void powers(double x, double * x2, double * x3, double * x4){
    * x2 = x * x;
    * x3 = *x2 * x;
    * x4 = *x3 * x;
}

Zeile 8 In der main()-Funktion werden die Variablen x2, x3, x4 nur deklariert, aber nicht initialisiert.

Zeile 11: An der Ausgabe in printf() erkennt man, dass man jetzt dennoch auf die Werte x2, x3, x4 zugreifen kann. Initialisiert wurden sie durch die Berechnungen innerhalb der Funktion powers().

Zeile 17 bis 19: Man erkennt, dass man bei gleichzeitiger Verwendung der Multiplikation und des Indirektionsoperators sehr auf die Lesbarkeit des Quelltextes achten muss (Umdrehen der Multiplikationsreihenfolge w├╝rde nur zu Verwirrung f├╝hren).

Das Beispiel mag sehr gek├╝nstelt wirken, es zeigt aber den Mechanismus, wie man aus einer Funktion heraus mehrere Variablen initialisieren kann, ohne diese ausdr├╝cklich zur├╝ckzugeben.

Es sei aber auch davor gewarnt, dass man die Zeiger-├ťbergabe leicht ├╝bersehen kann. Denn bei einem fl├╝chtigen Blick auf die main()-Funktion k├Ânnte man meinen, die Variablen x2, x3, x4 werden nur deklariert, aber nirgends initialisiert.