45. Scoping und Lebensdauer

Variablen in C haben unterschiedliche Sichtbarkeit (Scope) und Lebensdauer.

Der Scope ist weitgehend wie in (neuerem) Python – eine Variable ist sichtbar ab ihrer Deklaration bis zum Ende des Blocks, in dem sie deklariert wurde (für lokale Variablen) oder bis zum Ende der Datei (für globale Variablen). Dabei ist ein Block in C einfach das Material zwischen einer öffnenden und einer schließenden geschweiften Klammer. Mithin ist es durchaus legal, am Anfang etwa eines Schleifenkörpers Variablen zu definieren – sie existieren dann nur innerhalb der Schleife.

Normale Variablen leben auf dem Stack und daher so lange, wie der Block, in dem sie definiert sind, läuft. Globale Variablen sowie als static deklarierte lokale Variablen leben bis zum Programmende.

Als static deklarierte Funktionen und globale Variablen sind nur in dem Modul sichtbar, in dem sie definiert wurden (d.h. ihre Namen tauchen in der Ausgabe von nm nicht auf). Die eine Verwendung des static-Schlüsselworts hat mit der anderen recht wenig zu tun.

In f ist das globale a sichtbar, nicht das aus main.

Weitere Speicherklassen:

  • extern Variable ist hier nur deklariert, die Definition ist anderswo – siehe Module
  • register Nicht mehr verwenden
  • auto Tut nichts, unverzierte Variablen haben die Speicherklasse auto, ist also überflüssig.

Ähnlich verwendet werden die type qualifiers

  • volatile Variable kann von außen geändert werden (z.B. Temperatursensor einer Hardware, shared memory)
  • const Variable darf nicht geändert werden. Der Const-Qualifier ist vor allem im Zusammenhang mit Prototypen von Funktionen interessant, er erlaubt die Definition von “read-only”-Interfaces. Außerdem kann der Compiler consts besser optimieren.
  • restrict Ist ein Tipp an den Compiler, dass der Speicher, auf den ein restricted pointer zeigt, nur durch diesen Pointer verändert wird

Die type qualifier, allen voran restrict, tendieren zu leichter Esoterik, zumal, wenn sie in Kombination und in komplexeren Deklarationen auftauchen. Nach

const char * foo;
char const * bar;

darf man etwa an foo herumschrauben, nicht jedoch an *foo, während man an bar nicht herumschrauben darf (fast, als wäre es als char bar[] deklariert), wohl aber an *bar.

Dennoch lohnt es sich, const in Prototypen zu verwenden, wann immer möglich, weil es saubere Strukturen fördert und einige Programmierfehler zu vermeiden hilft.

Der volatile-modifier ist vor allem bei eher nahe am System liegenden Programmieraufgaben praktisch und bewahrt den Compiler vor Optimierungen, die sich darauf verlassen, dass er den Speicher unter Kontrolle hat. Auch im Zusammenhang mit Threads und Signalen braucht man unter umständen ein volatile.

Der restrict-modifier kann von Compilern fröhlich ignoriert werden. Wenn euch restrict unsympathisch ist und ihr auf das letzte Prozent Geschwindigkeit verzichten könnt, könnt ihr den Compilern folgen.

Beispiele dafür:

#include <stdio.h>

void countCalls(const char * const message)
/* message ist const, wir dürfen also den Pointer nicht
verändern, und es zeigt auf const char, wir dürfen also
auch die Nachricht nicht verändern. */
{
  static int counter=0; /* wird nur ganz am Anfang initialisiert,
   bleibt dazwischen erhalten */

  counter++;
  printf("%s %d\n", message, counter);
  /* Das würde einen Fehler geben: message = "Oops";
     und das auch: *message = 0;
  */
}

int main(void)
{
  countCalls("erster Aufruf");
  countCalls("zweiter Aufruf");
  countCalls("upzinkta Aufruf");
  return 0;
}

Die Ausgabe davon:

erster Aufruf 1
zweiter Aufruf 2
upzinkta Aufruf 3


Markus Demleitner

Copyright Notice