static (Schlüsselwort)

aus Wikipedia, der freien Enzyklopädie

static (in Visual Basic Static und Shared) ist ein Schlüsselwort in diversen Programmiersprachen wie z. B. Java, C, C++, C#, Visual Basic Classic und Visual Basic .NET. Das Schlüsselwort ist ein Zusatz bei der Deklaration von Variablen und Funktionen. Es hat in verschiedenen Kontexten eine sehr unterschiedliche Bedeutung, die die Lebensdauer, den Linker und Klassen beeinflussen oder lediglich Kompilierwarnungen auslösen können. In C und C++ gehört es zu den Schlüsselwörtern, die die meisten unterschiedlichen Bedeutungen haben. In C# und VB(.Net) ist die Bedeutung hingegen eindeutig. Dieser Artikel behandelt die Verwendung in den oben genannten (häufig verwendeten) Sprachen. Das Schlüsselwort kann auch in anderen (weniger häufig verwendeten) Programmiersprachen vorkommen und hat dort meist ebenfalls eine der hier aufgeführten Bedeutungen.

Verwendung

Verwendung Programmierart Ort der Verwendung Programmiersprache
Speicherklasse (storage duration) prozedural Variable auf Methodenebene C, C++, VB (Static) und VB.NET (Static)
Linkage prozedural Variable oder Methode auf Modul/Dateiebene C, C++
Klassenmember ohne Instanzbindung objektorientiert Variable oder Methode auf Klassenebene C++, Java, VB.NET (Shared), C#
Mindestgröße für Array-Parameter neutral Array-Definition in der Signatur einer Methode C99

static als Speicherklasse

Die Speicherklasse (englisch storage class) static definiert die „Lebenszeit“ einer Variable.

Die Lebenszeit einer globalen Variablen (Variablen auf Modul-/Dateiebene) beginnt mit dem Programmstart (bei globalen Variablen) und endet mit dem Ende des Programms: Sie haben stets die Speicherklasse „static“ (engl. static storage duration). Hingegen kann man auf Methodenebene mithilfe des Schlüsselwortes static die Speicherklasse beeinflussen. Standardmäßig ist die Lebensdauer von Variablen auf Methodenebene auf den Methodenaufruf beschränkt, sie werden also beim Verlassen der Methode wieder entfernt. Variablen mit dem Schlüsselwort static besitzen hingegen die Speicherklasse static und behalten ihre Werte daher auch über den Methodenaufruf hinweg. Das Gegenstück zum static-Schlüsselwort in diesem Kontext ist das Weglassen des Schlüsselwortes (oder in C das Verwenden des auto-Schlüsselwortes).

Bei rein prozeduralen Programmiersprachen wird das Schlüsselwort in diesem Kontext gerne verwendet, um eine möglichst geringe Sichtbarkeit der Variablen zu erreichen. Alternative zu einer Variable in einer Methode mit statischer Speicherklasse ist eine Variable auf Modul-/Dateiebene, die durch den Linker nicht zur Verfügung gestellt wird (siehe #Linkage). Diese ist jedoch auch von einer anderen Methode im selben Modul/Datei zugreifbar.

In objektorientierten Programmiersprachen stehen für eine möglichst geringe Sichtbarkeit gut geeignete Alternativen zur Verfügung. Zudem erfordern die Kapselungskonzepte häufig auch methodenübergreifenden Zugriff. Größte Einschränkung ist aber, dass Variablen häufig im Klassenkontext benötigt werden, während Variablen der static storage duration klassenübergreifend arbeiten. Daher wird das Schlüsselwort in dieser Bedeutung dort erheblich seltener verwendet. Benötigt man doch static storage duration setzt man stattdessen auf private statische (siehe #static und Shared bei Klassenmembern) Klassenvariablen, die auch für andere Methoden der Klasse zugreifbar sind, aber dafür viel besser in die Systematik des objektorientierten Programmierens passt.

Die Bedeutung von static als Speicherklasse kann in C und C++ angewendet werden. In Visual Basic Classic wurde das Schlüsselwort (übliche Schreibweise: Static) ebenfalls eingeführt und in Visual Basic .Net aus Kompatibilitätsgründen übernommen. C# hingegen verzichtet zugunsten einer eindeutigen Bedeutung des static-Schlüsselwortes auf diese Bedeutung des Schlüsselwortes.

Beispiel in C und C++

int summiere(int s)
{
    static int summe = 100; // Variable wird beim ersten Durchlauf der Deklaration initialisiert.
    summe += s;
    return summe;
}

Beispiel in VB.Net und VB

VB.Net:

Public Function Summiere(ByVal s As Integer) As Integer
  Static summe As Integer = 100
  summe += s
  Return summe
End Function

VB funktioniert unter Berücksichtigung der fehlenden Syntaxelemente (etwa dem fehlenden Return) analog, siehe zum Beispiel für VB6.[1]

Linkage

Die Linkage eines Symbols (wie z. B. eines Variablen- oder Funktionsnamens) gibt an, wie mit dem Symbol beim Linken umgegangen wird.

Globale Variablen haben in C standardmäßig external linkage, das heißt, sie sind von anderen Übersetzungseinheiten aus sichtbar und existieren nur einmal im endgültigen Programm. Alle Methoden greifen auf dieselbe Variable zu. Wird ein Symbol mit external linkage in mehreren Übersetzungseinheiten definiert (und nicht bloß deklariert!), gibt es beim Linken einen Linkerfehler (double definition error), da unklar ist, welche der beiden Variablen in allen übrigen Übersetzungseinheiten verwendet werden soll.

Um Variablen und Funktionen jeweils pro Übersetzungseinheit zu definieren, muss man sie mit internal linkage deklarieren. Dies geschieht in C und C++ durch das Schlüsselwort static bei der Deklaration einer globalen Variable oder Funktion. Die Variable/Funktion wird nun dem Linker nicht mehr zur Verfügung gestellt, so dass die anderen Übersetzungseinheiten nicht mehr (direkt) auf sie zugreifen können (Zugriffsmodifikator). Gleichzeitig besteht dadurch für andere Übersetzungseinheiten die Möglichkeit, Variablen und Funktionen zu definieren, die zwar denselben Namen, aber dennoch nichts miteinander zu tun haben.

Die Verwendung des static-Schlüsselwortes zu Linkage-Zwecken existiert nur in C und C++. Java, C# und VB(.Net) haben keine Linker in dieser Form und erlauben die Definition von Variablen auf Modul/Dateiebene auch nicht.

Bei prozeduraler Programmierung gilt die Verwendung des Schlüsselwortes bei allen Methoden, die nicht von außen zugreifbar sein sollen, und bei allen Variablen, als guter Stil. (Bei Bedarf erfolgt der Zugriff auf die Variablen über einen Getter und einen Setter als Zugriffsfunktion.) Bei der Trennung zwischen Modulinterface (.h-Datei) und Code (.c-Datei) werden intern gelinkte Member im Gegensatz zu extern gelinken nicht in der .h-Datei deklariert, sondern befinden sich ausschließlich in der .c-Datei.

Beispiel in C oder C++

Das folgende Beispiel zeigt, auf welche Weise static beim Linken eingesetzt wird, und warum das Schlüsselwort in dieser Bedeutung überhaupt notwendig ist.

Übersetzungseinheit foo.c
    static int x; // internal linkage
    extern int y; // nur Deklaration!
                  // => nicht alleine Lauffähig.
    int z;        // external linkage in beiden Dateien

    const int c = 42; // external linkage in C, internal in C++

    static int f() {return 0;} // internal linkage
    int g();                   // nur Deklaration!
                               // => nicht alleine Lauffähig.
    int h() {return 2;}        // external linkage in beiden Dateien
Übersetzungseinheit bar.c
    static int x; // internal linkage
    int y;        // external linkage, ergänzt beim Zusammenlinken
                  // die Deklaration in foo.c um die Definition.
    int z;        // external linkage in beiden Dateien => Fehler

    const int c = 42; // external linkage in C, internal in C++

    static int f() {return 0;} // internal linkage
    int g() {return 1;}        // external linkage, ergänzt beim Zusammenlinken
                               // die Deklaration in foo.c um die Definition.
    int h() {return 2;}        // external linkage in beiden Dateien => Fehler

Die Variablen x und die Funktionen f() sind in beiden Dateien static. Sie werden nach dem Kompilieren dem Linker nicht zur Verfügung gestellt und haben daher trotz ihres gleichen Namens nichts miteinander zu tun.

Die Variable y und die Funktion g() sind in der Datei foo.c mit extern bzw. ohne Methodenrumpf deklariert. Die Datei foo.c ist daher nicht alleine lauffähig, stattdessen wird in der kompilierten Datei der Linker dazu aufgefordert, die fehlende Funktion bzw. Variable aus der bar.c-Datei einzufügen. Die Datei bar.c wäre hingegen auch ohne Verlinken lauffähig, der Compiler stellt die Variablen y und die Funktionen g() der bar.c-Datei dem Linker zur Verfügung, so dass dieser die fehlenden Symbole in foo.c ergänzen kann. Sobald beide Dateien zusammengelinkt sind, verwenden beide Dateien dieselbe Variable bzw. dieselbe Funktion.

Die Variablen z und die Funktionen h() sind in beiden Dateien definiert und werden in beiden Dateien dem Linker zur Verfügung gestellt. Versucht man, diese beiden Dateien zu kompilieren und anschließend zusammenzulinken, gibt es Linkerfehler für die Variable z und die Funktion h(), da diese jeweils zweimal definiert wurden, womit unklar wäre, welche der beiden Instanzen genutzt werden soll.

C und C++ verhalten sich unterschiedlich bei der Default-Linkage der const-Variablen c, bei der kein Schlüsselwort angegeben wurde. Während diese bei C weiterhin external linkage hat und somit obiges Beispiel auch für c einen Linkerfehler geben würde (analog zur Variablen z), hat sie bei C++ internal linkage und existieren somit pro Übersetzungseinheit. Somit gäbe es bei C++ keinen Linkerfehler.

static und Shared bei Klassenmembern

In Sprachen mit Klassen kann es Klassenfunktionen geben, die etwas mit der Klasse zu tun haben, aber keinerlei Daten von Instanzen dieser Klasse benötigen. Auch kann es Klassen und Variablen geben, bei denen es aus technischen Gründen nicht sinnvoll ist, mehr als eine Instanz anzulegen. Zu diesem Zweck können Deklarationen von Membervariablen und -funktionen mit dem Schlüsselwort static versehen werden. Es zeigt an, dass diese Variable oder Funktion ohne eine Instanz der Klasse existiert und benutzt werden kann. Dies stellt in objektorientierten Sprachen meist die häufigste Verwendung des Schlüsselwortes dar.

In C++, C# und Java wird das static-Schlüsselwort zu diesem Zweck verwendet, VB.Net setzt hingegen das Shared-Schlüsselwort (übersetzt: (mit anderen Klasseninstanzen) geteilt) für diesen Zweck ein, um eine kontextabhängige Bedeutung des Static-Schlüsselwortes zu vermeiden, nachdem dieses aus Kompatibilitätsgründen zu VB Classic bereits in der Bedeutung als Speicherklasse verwendet wird.

Statische Variablen und Methoden werden aufgrund ihres Zweckes ohne Klasseninstanz aufgerufen, so dass häufig auch keine Instanz zur Verfügung steht, über die man die Methode aufrufen könnte. Die übliche Syntax Variable.Methode() bzw. Variable->Methode() fällt daher weg. Um dennoch auf sie zugreifen zu können, ruft man die Methoden über den Namen der Klasse auf (Klasse.Methode() bzw. Klasse::Methode()). Um zu verhindern, dass der Programmierer die Auswirkung seiner Programmierung falsch einschätzt, ist in einigen Programmiersprachen der Aufruf eines statischen Klassenmembers über eine Instanz verboten oder es werden Kompilierwarnungen erzeugt.

Statische Konstruktoren

Variablen, die nicht an eine Klasseninstanz gebunden sind, können nicht über normale Konstruktoren initialisiert werden. Die Sprachen setzen dabei verschiedene Optionen ein, wie derartige Variablen initialisiert werden können: In C++ stellt die Variable nur eine Deklaration dar, die an späterer Stelle (analog zu Variablen auf Modulebene meist in der cpp-Datei, damit die Variable nur einmal im kompilierten Programm vorhanden ist) definiert wird und mit einem Standardwert versehen werden kann. In Java, C# und VB.Net werden die statischen Member hingegen direkt oder über Konstruktor-ähnliche Methoden initialisiert (statische Initializer, statische Konstruktoren). Diese Konstruktoren besitzen keine Parameter und werden implizit einmalig vor dem ersten Aufruf eines Klassenmembers oder dem Erstellen einer Klasseninstanz aufgerufen. Diese statischen Initializer werden ebenfalls mit dem Schlüsselwort static bzw. Shared als solche gekennzeichnet, im Detail unterscheidet sich die Syntax jedoch zwischen Java und C# bzw. VB.Net.

Beispiel in C++

//Header:
class C
{
public:
    int i;
    static int s;  // nur Deklaration!
};

//Source:
int C::s = 42; // Definition & Initialisierung der statischen Membervariablen

int usage()
{
    C::s = 1; // Benutzung ohne ein Objekt vom Typ C
    C c;
    c.i = 17;
    c.s = 0;  // möglich, aber Gefahr dass der Programmierer die Auswirkungen falsch einschätzt. Compiler kann hier warnen.
}

Beispiel in Java

Eine Trennung zwischen Deklaration und Definition ist in Java, C# und VB.Net nicht nötig, der Compiler und die Laufzeitumgebung kümmern sich selbstständig darum, dass die Variable nur einmal im ausführenden Programm vorkommt.

class Beispiel {
  public static int s; // statische Variable, Initialisierung im statischen Initializer.
  public static int t = 5; // statische Variable mit Standardwert
  public int i; // Klassenvariable, Initialisierung im Konstruktor
  public Beispiel() { // Konstruktor, Initialisierung von i im Konstruktor.
    i = 0;
  }
  static { //Statischer Initializer: Syntax in Java: nur ein static-Schlüsselwort
    s = 42; //Statischer Initializer, Initialisierung von s im statischen Initializer
  }
  public static void usage() {
    Beispiel.s += 1; //Aufruf über den Klassennamen
    Beispiel b = new Beispiel();
    b.i += 1; //Aufruf der Klassenvariable über eine Instanz.
    b.s += 1; // Keine Kompilierwarnung in Java.
  }
}

Beispiel in C#

class Beispiel
{
  public static int s; // statische Variable, Initialisierung im statischen Konstruktor.
  public static int t = 5; // statische Variable mit Standardwert
  public int i; // Klassenvariable, Initialisierung im Konstruktor
  public Beispiel() // Konstruktor, Initialisierung von i im Konstruktor.
  {
    i = 0;
  }
  static Beispiel() // statischer Konstruktor: Syntax in C#: Konstruktor mit static-Schlüsselwort und ohne Parameter und Zugriffsmodifikator
  {
    s = 42; // Initialisierung von s im statischen Konstruktor
  }
  public static void Usage()
  {
    Beispiel.s += 1; // Aufruf über den Klassennamen
    Beispiel b = new Beispiel();
    b.i += 1; // Aufruf der Klassenvariable über eine Instanz.
    b.s += 1; // Erzeugt einen Kompilierfehler (CS0176), um Fehleinschätzungen zu vermeiden.
  }
}

Beispiel in VB.NET

Public Class Beispiel
  Public Shared s As Integer 'Statische Variable, Initialisierung im statischen Konstruktor.
  Public Shared t As Integer = 5 'Statische Variable mit Standardwert
  Public i As Integer 'Klassenvariable, Initialisierung im Konstruktor
  Public Sub New() 'Konstruktor, Initialisierung von i im Konstruktor.
    i = 0
  End Sub
  Shared Sub New() 'Statischer Konstruktor: Syntax in VB.Net: Konstruktor mit Shared-Schlüsselwort und ohne Parameter und Zugriffsmodifikator
    s = 42 'Statischer Konstruktor, Initialisierung von s im statischen Konstruktor
  End Sub
  Public Shared Sub Usage()
    Beispiel.s += 1 'Aufruf über den Klassennamen
    Dim b As New Beispiel()
    b.i += 1 'Aufruf der Klassenvariable über eine Instanz.
    b.s += 1 'Erzeugt eine Kompilierwarnung (BC42025), um Fehleinschätzungen zu vermeiden.
  End Sub
End Class

static bei Array-Parametern

Da Arrays in C als „Zeiger auf das erste Element“ an Funktionen übergeben werden, ist der Funktion die Größe des Arrays nicht bekannt. In C99 wurde die Möglichkeit geschaffen, bei der Deklaration der Funktion eine Mindestgröße mitzugeben. Dies erlaubt zum einen eine bessere Typprüfung zur Kompilierzeit und – vor allem im Zusammenhang mit dem restrict-Schlüsselwort – die Erzeugung performanteren Codes.

Ähnliche Prüfungen können in VB.Net und in C# ab .Net 4.0 im Rahmen der weit mächtigeren Codeverträge durchgeführt werden. Die static-Syntax in der Signatur der Methode, wie in C, wird dabei jedoch nicht unterstützt. Auch in C++ wurde diese Bedeutung nicht übernommen.

Beispiel
void hash(unsigned char digest[static 64], const char* data, size_t size);

int main()
{
    unsigned char buffer[32];
    hash(buffer, data, length);  // Compilerwarnung: hash erwartet mind. 64 Bytes, buffer ist aber kleiner!
}

Weblinks

Einzelnachweise

  1. Peter Aitken: Preserve procedure variables with Static in VB6. techrepublic.com. 8. September 2005. Abgerufen am 8. November 2019.