Praca z pamięcią w statycznym obszarze przechowywania w języku C to ważna część zrozumienia cyklu życia zmiennych i zasobów programu.
Historia pytania
W C wyróżnia się obszary przechowywania zmiennych: automatyczny (stos), dynamiczny (heap) i statyczny (segment danych/bss). Statyczny obszar przechowywania to obszar pamięci zarezerwowany dla zmiennych, które istnieją przez cały czas wykonywania programu. Umieszczane są w nim zmienne zadeklarowane ze specyfikatorem static (w środku i na zewnątrz funkcji) oraz zmienne globalne.
Problem
Błędy związane z obszarem przechowywania występują przy niewłaściwym zarządzaniu czasem życia zmiennych, próbach wielokrotnej inicjalizacji czy błędnym założeniu o dostępie wielowątkowym. Często myli się też pamięć statyczną z dynamiczną lub automatyczną, szczególnie przez nowicjuszy.
Rozwiązanie
Zmiennne statyczne są przechowywane w segmentach danych (lub bss, jeśli nie są zainicjowane). Inicjalizowane są raz przed rozpoczęciem wykonywania main() i zachowują wartość między wywołaniami funkcji, ale są niedostępne poza swoim zasięgiem, jeśli zadeklarowane z static wewnątrz funkcji lub pliku. Używane są do przechowywania stanu między wywołaniami lub do realizacji prywatności danych.
Przykład kodu:
#include <stdio.h> void counter() { static int count = 0; count++; printf("Called %d times\n", count); } int main() { for (int i = 0; i < 3; i++) counter(); return 0; }
Kluczowe cechy:
Czy zmienne statyczne mogą być lokalne i globalne? Jaka jest różnica?
Tak, lokalne zmienne statyczne są zadeklarowane wewnątrz funkcji, globalne — na zewnątrz wszystkich funkcji. Lokalne są widoczne tylko wewnątrz funkcji, globalne — w całym pliku (jeśli static jest wskazane przed zmienną globalną, to jest ona "prywatna" dla pliku).
Przykład kodu:
static int g_val = 42; // dostępny w całym tym pliku void foo() { static int count = 0; // widoczny tylko w foo i żyje przez cały czas działania programu }
Kiedy dokładnie jest inicjalizowana zmienna statyczna: przy każdym wejściu do funkcji, przy pierwszym wywołaniu czy przed startem main?
Wszystkie zmienne statyczne (globalne lub lokalne, zadeklarowane z static) są inicjalizowane przed startem main(), czyli w trakcie ładowania programu. Jeśli inicjalizacja jest jawna, używane jest podane wartości, w przeciwnym razie zmienna jest inicjalizowana zerem.
Czy można zadeklarować tablicę zmiennych z modyfikatorem static wewnątrz ciała funkcji? Jak ona się zachowa?
Tak, można. Taka tablica zachowa wartości między wywołaniami funkcji, a przy pierwszym wywołaniu będzie zainicjowana zerami (jeśli nie podano inaczej).
Przykład kodu:
void bar() { static int arr[3]; // wszystkie elementy będą równe 0 przy pierwszym wywołaniu arr[0]++; printf("arr[0]=%d\n", arr[0]); }
Zalety: Wygodnie przechowywać stan między wywołaniami funkcji, można realizować "prywatne" dane, nie trzeba ręcznie rezerwować/zwalniać pamięci.
Wady: Nie nadaje się do programów bezpiecznych dla wątków bez dodatkowej synchronizacji, błędnie używać do przechowywania dużych ilości danych, mogą prowadzić do nieprzewidywalnego zachowania przy niepoprawnej zmianie wartości.
Negatywny przypadek: Programista przechowuje tymczasową roboczą kopię ogromnej tablicy w static wewnątrz funkcji. W rezultacie aplikacja zawsze zajmuje ogromną ilość pamięci, nawet gdy ta tablica nie jest potrzebna. Plus: łatwość dostępu, minus: wysokie zużycie pamięci, brak jawnego zarządzania alokacją.
Pozytywny przypadek: Statyczny licznik wywołań funkcji jest używany do diagnostyki i profilowania (zob. przykład powyżej). Plus: nie wymaga zmiennych globalnych, minus: należy zachować ostrożność przy wielowątkowości — wymagana jest synchronizacja.