Tracks
/
C
C
/
Lehrplan
/
Blöcke
Bl

Blöcke in C

{one: "1 Übung", other: "%{count} Übungen"}

Über Blöcke

Ein Block ist eine Menge von Anweisungen und Deklarationen, die zu einer syntaktischen Einheit zusammengefasst sind. Blöcke werden oft, aber nicht immer, in geschweiften Klammern definiert. Blöcke können in Funktionen und in anderen Blöcken verschachtelt sein.

Im folgenden Beispiel wird number innerhalb der Funktion main definiert. flag wird innerhalb des if-Blocks definiert, der in die Funktion main eingebettet ist. Variablen, die in einem Block definiert werden, sind nur innerhalb dieses Blocks gültig. Wenn man den Code also zu kompilieren versucht, führt das zu einem Kompilierungsfehler.

#include <stdio.h>

int main() {

  int number = 1;

  if (number == 1) {
      int flag = 1;
      printf("%d", flag);
  }

  // this line generates a compilation error
  // error: 'flag' undeclared (first use in this function)
  printf("%d", flag);
    
}

Die Fehlermeldung kann verwirrend sein, denn flag wird ja weiter oben in der Funktion verwendet. Allerdings wird flag innerhalb eines Blocks in der Funktion definiert. Der innere Block kann die Variable number in seiner äußeren Funktion „sehen“, aber die äußere Funktion kann die Variable flag, die in ihrem inneren Block definiert ist, nicht „sehen“. Sobald die Ausführung den Block verlässt, wird die Variable flag automatisch zerstört und steht dem Programm nicht mehr zur Verfügung. Die Funktion main selbst sieht den Bezeichner flag zum ersten Mal in der letzten printf-Anweisung. Wenn die letzte printf-Anweisung entfernt wird, lässt sich das Programm kompilieren und gibt bei der Ausführung 1 aus. Das liegt daran, dass die Variable flag in dem Block gültig ist, der die erste printf-Anweisung enthält.

Seit C99 gelten auch einzeilige if-, for-, while- und do-Anweisungen als Blöcke. Im folgenden Beispiel wird i zwar nicht in geschweiften Klammern definiert, existiert aber nur in dem Block, der durch die for-Anweisung definiert wird, und ist für die äußere Funktion main nicht sichtbar.

#include <stdio.h>

int main() {

    for (int i = 0, j = 11; i < j; i+=2)
        printf("%d", i);

    // this line generates a compilation error
    // error: 'i' undeclared (first use in this function)
    printf("%d", i);
    
}

Wenn die letzte printf-Anweisung entfernt wird, lässt sich das Programm kompilieren und gibt bei der Ausführung 0 2 4 6 8 10 aus. Einige Sprachen wie Go und Rust legen ausdrücklich fest, dass einzeilige Bedingungs- und Schleifenanweisungen einen Block bilden. Sie verlangen, dass solche einzeiligen Anweisungen trotzdem in geschweifte Klammern gesetzt werden.

Eine Funktion verhält sich in gewisser Weise wie ein Block. Ein Bezeichner für eine in einer Funktion definierte Variable ist außerhalb der Funktion nicht sichtbar, genauso wie ein Bezeichner für eine in einem Block definierte Variable außerhalb des Blocks nicht sichtbar ist. Zwei Variablen in zwei Funktionen können denselben Namen haben, weil die beiden Funktionen nicht ineinander hineinsehen, genauso wie zwei Variablen in zwei Blöcken denselben Namen haben können, weil die beiden Blöcke nicht ineinander hineinsehen. Aber Funktionen und Blöcke sind nicht identisch. Eine Funktion kann nicht innerhalb einer anderen Funktion definiert werden, ein Block aber sehr wohl in einen anderen Block verschachtelt werden. Und einige Bezeichner in einem Block können außerhalb des Blocks sichtbar sein. Im folgenden Beispiel lässt sich der Code nicht kompilieren

#include <stdio.h>

int main() {

  {
      int i = 0;
      begin:
      printf("i == %d\n", i);
      if (i == 0) {
          i = 1;
          goto begin;
      }
  }

  {
      int i = 1;
      begin:
      printf("i == %d\n", i);
      if (i == 1) {
          i = 0;
          goto begin;
      }
  }

}

Der obige Code lässt sich nicht kompilieren und meldet den folgenden Fehler

/tmp/gH2TVYnTSJ.c: In function 'main':
/tmp/gH2TVYnTSJ.c:17:5: error: duplicate label 'begin'
   17 |     begin:
      |     ^~~~~
/tmp/gH2TVYnTSJ.c:7:5: note: previous definition of 'begin' was here
    7 |     begin:
      |     ^~~~~

Obwohl i in jedem Block definiert werden kann, müssen label-Namen innerhalb einer Funktion eindeutig sein. Das liegt daran, dass labels den Gültigkeitsbereich der Funktion haben: Jedes Label ist für die Funktion sichtbar, egal wo in der Funktion es definiert wird. Anders als Variablen muss ein label also, egal wo es in einer Funktion definiert wird, innerhalb derselben Funktion einen eindeutigen Namen haben. Damit der Code funktioniert, muss man den Namen eines der labels ändern, sodass beide eindeutig sind, wie im folgenden Beispiel

#include <stdio.h>

int main() {

    {
        int i = 0;
        begin:
        // prints
        // i == 0
        // i == 1
        printf("i == %d\n", i);
        if (i == 0) {
            i = 1;
            goto begin;
        }
    }

    {
        int i = 1;
        start:
        // prints
        // i == 1
        // i == 0    
        printf("i == %d\n", i);
        if (i == 1) {
            i = 0;
            goto start;
        }
    }

}

Einzeilige Anweisungen lassen sich „stapeln“ und trotzdem ohne geschweifte Klammern in einem Block unterbringen. Im folgenden Beispiel enthält die for-Anweisung eine if-Anweisung, die wiederum eine printf-Anweisung enthält. Obwohl der for-Block insgesamt zwei Anweisungen enthält, wird nur seine einzeilige if-Anweisung als Schleifenblock der for-Schleife ausgewertet. Die einzeilige printf-Anweisung der if-Anweisung wird wiederum als deren Block ausgewertet. Wenn verschachtelte Blöcke jeweils nur eine Zeile umfassen, braucht keiner der Blöcke geschweifte Klammern. Beachte, dass der Kommentar keine geschweiften Klammern braucht, um ihn und die printf-Anweisung im if-Block einzuschließen, denn ein Kommentar ist keine Anweisung. Die Einrückung macht deutlich, was passiert, aber ein Entwicklungsteam hat vielleicht eigene Coding-Richtlinien dazu, wo in solchen Fällen geschweifte Klammern stehen, um jede Art von Mehrdeutigkeit zu vermeiden.

#include <stdio.h>

int main() {

    for (int i = 0, j = 11; i < j; i++)
        if (i & 1 == 1)
            // prints 1 3 5 7 9 
            printf("%d ", i);
}

Wir haben gesehen, dass auf Werte, die innerhalb eines Blocks definiert werden, von außerhalb des Blocks nicht zugegriffen werden kann. Und wir haben gesehen, dass auf Werte, die außerhalb eines Blocks definiert werden, normalerweise von innerhalb des Blocks zugegriffen werden kann. Es ist möglich, dass eine Variable außerhalb eines Blocks innerhalb eines Blocks verdeckt wird, wenn der Bezeichner dieser Variable beim Definieren einer Blockvariable verwendet wird. Im folgenden Beispiel wird der Bezeichner number sowohl für eine Variable auf Funktionsebene als auch für eine Variable auf Blockebene verwendet. Wir sagen, dass die Variable auf Blockebene die Variable auf Funktionsebene verdeckt oder sie beschattet.

#include <stdio.h>

int main() {
    int number = 1;
    // prints number in function is 1
    printf("number in function is %d\n", number);
    
    if (number == 1) {
        // the definition of a block-level variable using the same name
        // shadows the function-level variable
        int number = 2;
        // prints number in function is 2
        printf("number in if block is %d\n", number);
    }
    // prints number in function is 1
    printf("number in function is %d\n", number);
}

Compiler können einen Fehler melden, wenn versucht wird, einen Funktionsparameter zu beschatten. Das folgende Beispiel

#include <stdio.h>

void print_number(int number) {
    // will fail to compile
    int number = 2;
    printf("number in function is %d\n", number);
}

int main() {
    int number = 1;
    printf("number in main is %d\n", number);
    print_number(number);
    printf("number in main is %d\n", number);
}

führt auf zwei Plattformen zu den folgenden Fehlern

/tmp/jIvhD5UOop.c: In function 'print_number':
/tmp/jIvhD5UOop.c:5:9: error: 'number' redeclared as different kind of symbol
    5 |     int number = 2;
      |         ^~~~~~
/tmp/jIvhD5UOop.c:3:23: note: previous definition of 'number' was here
    3 | void print_number(int number) {
      |                   ~~~~^~~~~~

und

redefinition of formal parameter 'number'
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab