Visits heute: 196
Aktuelle Seite: Speicherklassen in C
In C bestimmen Speicherklassen (storage classes) vor allem, wo und wie lange eine Variable existiert und welche Sichtbarkeit bzw. Verknüpfung (Linkage) sie besitzt.
Die wichtigsten Schlüsselwörter sind:
autoregisterstaticextern
Zusätzlich ist es wichtig, zwischen Speicherdauer (storage duration) und Sichtbarkeit/Linkage zu unterscheiden.
1. auto – die normale lokale Variable
auto ist für lokale Variablen die Standard-Speicherklasse.
void foo(void)
{
auto int x = 10;
int y = 20; // identisch, auto ist implizit
}
x und y sind hier praktisch dasselbe.
Eigenschaften
- lokal zur Funktion bzw. zum Block
- wird beim Eintritt in den Block erzeugt
- verschwindet beim Verlassen des Blocks
- bei jedem erneuten Aufruf neu erzeugt
- nicht automatisch initialisiert
Beispiel:
void counter(void)
{
int count = 0;
count++;
printf("%d\n", count);
}
Bei:
counter();
counter();
counter();
kommt:
1
1
1
Denn count wird bei jedem Aufruf neu erzeugt und mit 0 initialisiert.
In modernem C schreibt man
autofast nie explizit.int countist die übliche Schreibweise.
2. static innerhalb einer Funktion
Hier wird es interessant.
Eine lokale Variable mit static behält ihren Wert zwischen Funktionsaufrufen.
void counter(void)
{
static int count = 0;
count++;
printf("%d\n", count);
}
Jetzt:
counter();
counter();
counter();
ergibt:
1
2
3
Obwohl count weiterhin nur innerhalb von counter() sichtbar ist, wird sie nicht bei jedem Aufruf neu erzeugt.
Das Entscheidende
sichtbar? Lebensdauer
----------------------------------------------------
int count nur in Funktion bis Blockende
static int count nur in Funktion gesamtes Programm
Man kann sich static hier also als "lokale Variable mit dauerhaftem Gedächtnis" vorstellen.
Beispiel: Zustand einer Funktion
Das ist beispielsweise praktisch für eine Funktion, die wissen soll, wie oft sie aufgerufen wurde:
int get_next_id(void)
{
static int id = 0;
return ++id;
}
Dann:
printf("%d\n", get_next_id());
printf("%d\n", get_next_id());
printf("%d\n", get_next_id());
ergibt:
1
2
3
3. static auf Dateiebene
static hat außerhalb einer Funktion noch eine andere, sehr wichtige Bedeutung.
Betrachten wir:
/* motor.c */
static int speed = 100;
Diese Variable ist nur innerhalb von motor.c bekannt.
Sie kann also nicht von anderen Übersetzungseinheiten aus angesprochen werden.
Das Gleiche gilt für Funktionen:
static void calculate_speed(void)
{
...
}
calculate_speed() ist dann eine private Funktion dieses C-Moduls.
Das ist bei größeren C-Projekten extrem nützlich.
Beispiel
motor.c
#include "motor.h"
static int speed = 0;
static void update_pwm(void)
{
...
}
void motor_set_speed(int value)
{
speed = value;
update_pwm();
}
main.c
#include "motor.h"
int main(void)
{
motor_set_speed(50);
}
main.c kann motor_set_speed() verwenden.
Aber:
speed = 100;
geht nicht.
Und:
update_pwm();
geht ebenfalls nicht.
Damit kann man ein Modul sauber kapseln.
4. extern – eine Variable befindet sich woanders
extern bedeutet im Wesentlichen:
Diese Variable existiert, aber ihre Definition befindet sich an einer anderen Stelle.
Beispielsweise:
config.c
int debug_level = 2;
Das ist die Definition.
In einer anderen Datei:
main.c
extern int debug_level;
int main(void)
{
printf("%d\n", debug_level);
}
Hier ist debug_level nur deklariert.
Der Compiler bekommt damit die Information:
Es gibt irgendwo ein
int debug_level.
Der Linker findet später die eigentliche Definition in config.c.
Noch besser: Header-Datei
Typischerweise macht man:
config.h
#ifndef CONFIG_H
#define CONFIG_H
extern int debug_level;
#endif
config.c
#include "config.h"
int debug_level = 2;
main.c
#include <stdio.h>
#include "config.h"
int main(void)
{
printf("Debug-Level: %d\n", debug_level);
}
Das ist eine typische C-Projektstruktur.
5. register – bitte in Ruhe lassen
register ist historisch interessant.
register int i;
Damit konnte man dem Compiler früher einen Hinweis geben:
Diese Variable wird sehr häufig benutzt; leg sie möglichst in einem CPU-Register ab.
Früher war das durchaus relevant.
Heute optimieren moderne Compiler wesentlich besser:
gcc -O2
gcc -O3
Der Compiler entscheidet selbst, ob eine Variable in einem Register landet.
Deshalb schreibt man normalerweise einfach:
int i;
und überlässt die Optimierung dem Compiler.
6. Ein wichtiger Unterschied: Initialisierung
Hier lauert eine typische Falle.
Eine normale lokale Variable:
void foo(void)
{
int x;
printf("%d\n", x); // FEHLER: undefinierter Wert
}
hat keinen definierten Anfangswert.
Eine static-Variable dagegen wird automatisch mit 0 initialisiert:
void foo(void)
{
static int x;
printf("%d\n", x); // 0
}
Das gilt auch für globale Variablen:
int global;
global beginnt mit:
0
7. static wird automatisch initialisiert
Das gilt auch für komplexere Datentypen:
static int values[10];
Alle Elemente sind zunächst:
0 0 0 0 0 0 0 0 0 0
Auch:
static char buffer[1024];
ist vollständig mit '\0' initialisiert.
Das ist beispielsweise bei Puffern sehr praktisch.
8. Die vier Speicherklassen im Überblick
| Speicherklasse | Typischer Ort | Lebensdauer | Sichtbarkeit |
|---|---|---|---|
auto |
lokal | Block | Block |
register |
lokal | Block | Block |
static lokal |
lokal | gesamtes Programm | Block |
static global |
Datei | gesamtes Programm | nur diese Datei |
extern |
global | gesamtes Programm | über Dateien hinweg |
Dabei ist extern etwas anders als die anderen: Es beschreibt hauptsächlich eine Deklaration bzw. externe Verknüpfung, nicht einfach einen bestimmten Speicherort.
9. Besonders wichtig: static hat zwei Bedeutungen
Das ist wahrscheinlich die wichtigste Sache, die man sich merken sollte.
Innerhalb einer Funktion
void foo(void)
{
static int x;
}
bedeutet:
xbleibt über Funktionsaufrufe hinweg erhalten.
Außerhalb einer Funktion
static int x;
bedeutet:
xist nur in dieser Quelldatei sichtbar.
Und:
static void foo(void)
{
}
bedeutet:
foo()ist nur in dieser Quelldatei sichtbar.
10. Zusammenspiel in einem echten C-Modul
Nehmen wir beispielsweise ein kleines Motor-Modul:
motor.c
#include "motor.h"
static int current_speed = 0;
static void update_hardware(void)
{
printf("PWM = %d\n", current_speed);
}
void motor_set_speed(int speed)
{
current_speed = speed;
update_hardware();
}
int motor_get_speed(void)
{
return current_speed;
}
Hier sind:
static int current_speed;
und
static void update_hardware(void)
private Implementierungsdetails.
Nach außen gibt es nur:
void motor_set_speed(int speed);
int motor_get_speed(void);
Der Header könnte deshalb so aussehen:
#ifndef MOTOR_H
#define MOTOR_H
void motor_set_speed(int speed);
int motor_get_speed(void);
#endif
Das ist ein sehr schönes Beispiel dafür, wie man mit static in C eine Art Kapselung erreicht.
11. Speicherklasse ist nicht dasselbe wie Speicherort
Ein häufiger Denkfehler ist:
staticbedeutet "liegt im RAM".
Das ist so nicht ganz richtig.
Die Speicherklasse beschreibt Eigenschaften wie Lebensdauer und Linkage. Der tatsächliche Speicherort kann beispielsweise sein:
Stack
Heap
Data Segment
BSS
Register
...
Zum Beispiel:
void foo(void)
{
int a;
static int b;
}
Typischerweise könnte das vereinfacht so aussehen:
Stack:
a
statischer Speicher:
b
a verschwindet nach dem Verlassen von foo().
b bleibt während der gesamten Programmlaufzeit erhalten.
12. Ein schönes Gesamtbeispiel
#include <stdio.h>
int global = 10;
static int private_global = 20;
void test(void)
{
int normal = 0;
static int persistent = 0;
normal++;
persistent++;
printf("normal: %d\n", normal);
printf("persistent: %d\n", persistent);
}
int main(void)
{
test();
test();
test();
printf("global: %d\n", global);
printf("private: %d\n", private_global);
return 0;
}
Ausgabe:
normal: 1
persistent: 1
normal: 1
persistent: 2
normal: 1
persistent: 3
global: 10
private: 20
Der entscheidende Unterschied ist:
normal
│
├── Aufruf 1 → 0 → 1 → zerstört
├── Aufruf 2 → 0 → 1 → zerstört
└── Aufruf 3 → 0 → 1 → zerstört
persistent
│
├── Aufruf 1 → 0 → 1
├── Aufruf 2 → 1 → 2
└── Aufruf 3 → 2 → 3
Meine praktische Faustregel
Beim Programmieren in C brauchst du in der Praxis hauptsächlich diese drei Muster:
/* normale lokale Variable */
int value;
/* dauerhaft, aber nur innerhalb dieser Funktion */
static int value;
/* privat für dieses C-Modul */
static int value;
static void helper(void);
Und für Variablen, die zwischen mehreren .c-Dateien geteilt werden:
/* header.h */
extern int value;
/* source.c */
int value = 42;
register kannst du bei modernem C praktisch vergessen.
Wenn du C auf etwas fortgeschrittenerem Niveau betreibst, ist als nächstes besonders interessant, wie static, extern, const und volatile miteinander kombiniert werden – gerade bei deinen C-Projekten mit mehreren .c/.h-Dateien und Mikrocontrollern ist das sehr relevant.