MINT lernen

Übungen: Zeiger

Wer Adressen weitergibt, kann fremde Variablen ändern — hier trainierst du, wann das klappt und wann es kracht.

Dein Fortschritt:
0 / 0 Aufgaben
1

Übungsaufgaben

Zehn Übungen zu Zeigern, Dereferenzieren und Zeigerparametern — von AFB I bis AFB III. Jede Übung gibt dir sofort Rückmeldung; wenn du nicht weiterkommst, helfen die gestuften Tipps.

A1
Was bewirkt welche Zeile?
AFB I

Gegeben sind int t = 21; und ein Zeiger q. Ordne jeder Zeile zu, was sie bewirkt.

Ansatz: Ein Stern in einer Deklaration heißt „ist ein Zeiger“, ein Stern in einer Anweisung heißt „gehe zu der Adresse“.
Weiter: Auch eine Zeigervariable liegt irgendwo im Speicher und hat deshalb eine eigene Adresse.
A2
Stimmt’s? — Zeiger-Grundlagen
AFB I

Fünf Behauptungen zu int x = 4; int *p = &x;. Nenne jeweils, ob sie stimmt.

Fünf Aussagen nacheinander. Eine falsche Einschätzung reicht — dann startest du die Serie mit „Neue Runde“ neu.
Aussage 1 von 5

Merke dir die zwei Rollen: p ist die Adresse, *p ist der Wert dort. Wer *p = 9; für eine Adressänderung hält, verwechselt genau diese beiden.
Ansatz: Überlege bei jeder Aussage, ob es um die Adresse (p) oder um den Wert (*p) geht.
Weiter: Ein double * erwartet an der Adresse 8 Byte Kommazahl — bei einem int liegt dort aber etwas anderes.
A3
Was gibt das Programm aus?
AFB I
C · ausschnitt.c
int k = 4;
int *z = &k;
*z = *z * 3;
k = k + 1;
printf("%d\n", *z);

Gib die Ausgabe an.

Überlege selbst und trage das Ergebnis ein — Enter prüft direkt.
*z = *z * 3; macht aus k den Wert 12, danach erhöht k = k + 1; auf 13. Weil z immer noch auf k zeigt, liefert *z ebenfalls 13. Typischer Fehler: 12 — als ob *z eine Kopie von k wäre, die die spätere Änderung nicht mitbekommt.
Ansatz: *z und k sind zwei Namen für dieselbe Speicherstelle.
Weiter: Rechne Zeile für Zeile mit k: 4 → 12 → 13.
A4
Tracetabelle mit Zeiger
AFB II
C · trace.c
int m = 2, n = 8;     // Zeile 1
int *p = &m;          // Zeile 2
*p = *p + n;          // Zeile 3
p = &n;               // Zeile 4
*p = *p - m;          // Zeile 5
m = *p * 2;           // Zeile 6

Stelle den Ablauf als Tracetabelle dar. In der letzten Spalte trägst du den Namen der Variablen ein, auf die p zeigt (m oder n).

Fülle alle Felder aus und prüfe dann. Enter in einem Feld prüft ebenfalls.
nachmnp zeigt auf
Zeile 228m
Zeile 3
Zeile 4
Zeile 5
Zeile 6
Ab Zeile 4 zeigt p auf n: Zeile 5 rechnet also n = 8 − 10 = −2, und Zeile 6 setzt m = −2 · 2 = −4. Typischer Fehler: In Zeile 5 weiter mit m rechnen, weil p „am Anfang auf m zeigte“ — ein Zeiger zeigt immer auf die Variable der letzten Zuweisung an p.
Ansatz: Notiere nach jeder Zeile zuerst, wohin p gerade zeigt, und ersetze dann *p durch diese Variable.
Weiter: Zeile 3 heißt eigentlich m = m + n;, Zeile 5 heißt n = n - m;.
A5
Zwei Ergebnisse aus einer Funktion
AFB II

Eine Funktion soll von zwei Zahlen die kleinere und die größere liefern. Mit return geht nur ein Wert zurück — deshalb bekommt sie zwei Zeiger. Ergänze die Lücken.

Wähle in jedem Menü den passenden Eintrag und prüfe dann alle auf einmal.
C · minmax.c
void minmax(int a, int b, , int *gr) {
    if (a < b) {
        
        
    } else {
        *kl = b;
        *gr = a;
    }
}

int main(void) {
    int k, g;
    minmax(17, 9, );
    printf("%d %d\n", k, g);     // Ausgabe: 9 17
    return 0;
}
Die Funktion schreibt über *kl und *gr direkt in die Variablen k und g von main. Häufigster Fehler: beim Aufruf minmax(17, 9, k, g) — dann bekäme die Funktion nur die (zufälligen) Werte von k und g statt ihrer Adressen. int &kl gibt es erst in C++.
Ansatz: Wer in eine Variable eines anderen schreiben soll, braucht deren Adresse — Parameter also vom Typ int *.
Weiter: In der Funktion wird mit *kl = … geschrieben, beim Aufruf wird mit & die Adresse übergeben.
A6
Was sagt gcc dazu?
AFB II

Alle Zeilen stehen in main hinter int x = 1; und int *p = &x;; übersetzt wird mit gcc -Wall. Untersuche jede Zeile und lege sie in den passenden Korb.

Ziehe jede Karte in den passenden Korb — oder wähle sie mit Enter aus und drücke dann die Ziffer des Korbs (0 legt sie zurück).
1läuft fehlerfrei
2gcc warnt: Typen passen nicht
3übersetzt, stürzt aber ab
Bei p = x; und *p = &x; meldet gcc „makes pointer from integer“ bzw. „makes integer from pointer“: Adresse und Zahl werden vermischt. Die NULL-Zeilen übersetzt gcc ohne Meldung — der Fehler zeigt sich erst beim Ausführen als Segmentation fault. Typischer Fehler: zu glauben, der Compiler finde jeden Zeigerfehler.
Ansatz: Prüfe bei jeder Zuweisung: Welcher Typ steht links, welcher rechts? p ist int *, *p und x sind int, &x ist int *.
Weiter: Ob ein Zeiger gerade NULL ist, weiß der Compiler nicht — das stellt sich erst zur Laufzeit heraus.
A7
Kopie oder Zeiger?
AFB II Mix

Die Funktion bekommt einen Zeiger und einen gewöhnlichen int-Parameter — genau wie Parameter aus Kapitel 2.1.4 ist d eine Kopie.

C · schritt.c
void schritt(int *s, int d) {
    *s = *s + d;
    d = 0;
}

int main(void) {
    int pos = 3, w = 5;
    schritt(&pos, w);     // Aufruf 1
    schritt(&w, pos);     // Aufruf 2
    schritt(&pos, -w);    // Aufruf 3
    printf("%d %d\n", pos, w);
    return 0;
}

Wende den Code Aufruf für Aufruf an.

Arbeite die Kette Schritt für Schritt ab: Erst wenn ein Schritt stimmt, wird der nächste freigeschaltet. Enter prüft.
  1. pos nach Aufruf 1
  2. w nach Aufruf 1
  3. w nach Aufruf 2
  4. pos nach Aufruf 3
Nur die Variable, deren Adresse übergeben wird, ändert sich. d = 0; setzt lediglich die Kopie zurück — deshalb bleibt w nach Aufruf 1 bei 5. Typischer Fehler: nach Aufruf 1 für w 0 eintragen. Ausgabe am Ende: -5 13.
Ansatz: Markiere bei jedem Aufruf, welche Variable mit & übergeben wird — nur die kann sich ändern.
Weiter: Aufruf 2: w = 5 + 8. Aufruf 3: pos = 8 + (−13).
A8
Fehlersuche: drei Werte im Kreis
AFB III

rotiere soll drei Werte im Kreis verschieben: a bekommt den Wert von b, b den von c und c den alten Wert von a. Erwartete Ausgabe: 2 3 1. Überprüfe den Code — drei Zeilen sind fehlerhaft.

In diesem Code stecken Fehler. Klicke genau die fehlerhaften Zeilen an — die richtigen musst du stehen lassen.
Alle drei Fehler haben dieselbe Wurzel: Adresse und Wert verwechselt. Besonders tückisch ist c = alt;: Es ändert nur die Parameter-Kopie des Zeigers. Die Zeilen 3 und 4 sind richtig, weil dort auf beiden Seiten dereferenziert wird.
Ansatz: Gehe jede Zeile durch und frage: Steht hier eine Adresse (int *) oder ein Wert (int) — und passt das zur anderen Seite?
Weiter: Eine Zeile in rotiere liest falsch, eine schreibt falsch, und der Aufruf in main übergibt das Falsche.
A9
Wie viele Zeiger sind das?
AFB III Trick
C · deklaration.c
int x = 6;
int* p, q;

Tim sagt: „Wegen int* vorne sind p und q beide Zeiger.“ Beurteile die Aussagen und markiere alle zutreffenden.

Mehrere Antworten sind richtig. Markiere alle zutreffenden und klicke dann auf „Prüfen“.
Der Stern gehört zum Namen, nicht zum Typ davor: int* p, q; bedeutet int *p; int q;. Wo das Leerzeichen steht, ist egal. Deshalb schreiben viele den Stern direkt an den Namen — dann fällt ein fehlender zweiter Stern sofort auf.
Ansatz: Lies die Deklaration so, als stünde jede Variable in einer eigenen Zeile.
Weiter: Jeder Name, der ein Zeiger sein soll, braucht einen eigenen Stern.
A10
Quotient und Rest
AFB III

Die Funktion teile soll für z und n den ganzzahligen Quotienten und den Rest über zwei Zeiger zurückgeben; main ruft sie für 47 und 6 auf und gibt 47 = 7 * 6 + 5 aus. Der Rest wird dabei ohne % aus dem Quotienten berechnet. Entwirf das Programm, indem du die Zeilen ordnest.

Ziehe die Karten in die richtige Reihenfolge — mit der Tastatur: ↑/↓ verschiebt, Shift+↑/↓ wechselt nur den Fokus.
1void teile(int z, int n, int *q, int *r) {
2 *q = z / n;
3 *r = z - *q * n;
4} // Ende teile
5int main(void) {
6 int ganz, rest;
7 teile(47, 6, &ganz, &rest);
8 printf("47 = %d * 6 + %d\n", ganz, rest);
9 return 0;
10} // Ende main
Zwei Stellen verrutschen oft: *r = z - *q * n; braucht den schon geschriebenen Quotienten, muss also nach *q = z / n; stehen. Und teile muss vor main definiert sein, weil es keinen Prototyp gibt. 47 / 6 = 7 (Ganzzahldivision), 47 − 7 · 6 = 5.
Ansatz: Beginne mit der Funktion, die aufgerufen wird — ohne Prototyp muss sie vor main stehen.
Weiter: In main gilt: erst deklarieren, dann aufrufen, dann ausgeben.