Jaka jest różnica między deklarowaniem zmiennej poza pętlą a deklarowaniem statycznej pętli wewnętrznej?

9

Są dwa sposoby, w jakie mogę trzymać zmienną poza pętlą (lub dowolną funkcją).

Po pierwsze, mogę zadeklarować zakres globalny poza pętlą:

void setup()
{
    Serial.begin(9600);
}

int count = 0;

void loop()
{
    Serial.println(count);
    count++;

    delay(250);
}

Mogę również zadeklarować statyczność w pętli:

void setup()
{
    Serial.begin(9600);
}

void loop()
{
    static int count = 0;

    Serial.println(count);
    count++;

    delay(250);
}

Jaką różnicę to zrobi?

Cybergibbons
źródło

Odpowiedzi:

10

Najbardziej podstawowa różnica dotyczy zakresu.

W pierwszym przypadku deklarujesz zmienną globalną. Jest to zmienna, która jest dostępna w każdym zakresie po jej zdefiniowaniu.

void setup()
{
    Serial.begin(9600);
}

void inc();
int count = 0;

void loop()
{
    Serial.println(count);
    count++;

    inc();

    delay(500);
}

void inc() //Can edit the value of count
{
  count=count+1;
};

W drugim przypadku deklarujesz zmienną statyczną o zasięgu lokalnym. Zmienna będzie trwała przez cały czas działania programu podobnie jak zmienne globalne, ale będzie dostępna tylko w bloku kodu, w którym została zadeklarowana. Jest to ten sam przykład, z tylko jedną zmianą. countjest teraz zadeklarowany w środku jako zmienna statyczna loop.

void inc();

void loop()
{
    static int count = 0;
    Serial.println(count);
    count++;

    inc();

    delay(500);
}

To się nie skompiluje, ponieważ funkcja inc()nie ma dostępu do count.

Zmienne globalne, choć z pozoru przydatne, wiążą się z pewnymi pułapkami. Mogą nawet powodować szkody, jeśli chodzi o pisanie programów, które mogą wchodzić w interakcje z fizycznym otoczeniem. To bardzo prosty przykład czegoś, co może się wydarzyć, gdy tylko programy zaczną się powiększać. Funkcja może przypadkowo zmienić stan zmiennej globalnej.

void setup()
{
    Serial.begin(9600);
}
void another_function();
int state=0;

void loop()
{
    //Keep toggling the state
    Serial.println(state);
    delay(250);
    state=state?0:1;

    //Some unrelated function call
    another_function();
}

void another_function()
{
  //Inadvertently changes state
  state=1;

}

Takie przypadki są bardzo trudne do debugowania. Tego rodzaju problem można jednak łatwo wykryć, po prostu używając zmiennej statycznej.

void setup()
{
    Serial.begin(9600);
}
void another_function();

void loop()
{
    static int state=0;

    //Keep toggling the state
    Serial.println(state);
    delay(250);
    state=state?0:1;

    //Some unrelated function call
    another_function();
}

void another_function()
{
  //Results in a compile time error. Saves time.
  state=1;

}
asheeshr
źródło
5

Z perspektywy funkcjonalnej obie wersje generują ten sam wynik, ponieważ w obu przypadkach wartość countjest przechowywana między wykonaniami loop()(albo dlatego, że jest to zmienna globalna, albo dlatego, że jest oznaczona jako statici dlatego zachowuje swoją wartość).

Tak więc decyzja, którą wybrać, sprowadza się do następujących argumentów:

  1. Zasadniczo w informatyce zaleca się utrzymywanie zmiennych tak lokalnych, jak to możliwe pod względem zakresu . Zwykle skutkuje to znacznie wyraźniejszym kodem z mniejszymi skutkami ubocznymi i zmniejsza szanse, że ktoś użyje tej zmiennej globalnej, by popsuć logikę). Np. W pierwszym przykładzie inne obszary logiczne mogą zmienić countwartość, podczas gdy w drugim tylko ta konkretna funkcja loop()może to zrobić).
  2. Zmienne globalne i statyczne zawsze zajmują pamięć , podobnie jak miejscowi tylko wtedy, gdy są w zasięgu. W powyższych przykładach nie ma to żadnej różnicy (ponieważ w jednym używasz zmiennej globalnej, w drugiej zmiennej statycznej), ale w większych i bardziej złożonych programach może to i możesz oszczędzać pamięć za pomocą niestatystycznych ustawień lokalnych. Jednak : jeśli masz zmienną w obszarze logicznym, która jest wykonywana bardzo często, rozważ ustawienie jej na statyczną lub globalną, ponieważ w przeciwnym razie tracisz odrobinę wydajności za każdym razem, gdy ten obszar logiczny jest wprowadzany, ponieważ zajmuje to trochę czasu przydziel pamięć dla nowej instancji zmiennej. Musisz znaleźć równowagę między obciążeniem pamięci a wydajnością.
  3. Inne punkty, takie jak lepszy układ do analizy statycznej lub optymalizacja przez kompilator, również mogą mieć zastosowanie.
  4. W niektórych specjalnych scenariuszach mogą występować problemy z nieprzewidywalną kolejnością inicjowania elementów statycznych (nie jestem pewien co do tego punktu, porównaj to łącze ).

Źródło: Podobny wątek na arduino.cc

Philip Allgaier
źródło
Ponowne wejście nie powinno nigdy stanowić problemu w Arduino, ponieważ nie obsługuje współbieżności.
Peter Bloomfield
Prawdziwe. To był bardziej ogólny punkt, ale w rzeczywistości nie dotyczy Arduino. Usunąłem ten kawałek.
Philip Allgaier
1
Zmienna statyczna zadeklarowana w zakresie zawsze będzie istnieć i będzie używać tej samej przestrzeni co zmienna globalna! W kodzie OP jedyną różnicą jest to, jaki kod może uzyskać dostęp do zmiennej. W Scipe static będzie dostępny w tym samym zakresie.
jfpoilpret
1
@jfpoilpret To oczywiście prawda i widzę, że odpowiednia część mojej odpowiedzi była nieco myląca. Naprawiono to.
Philip Allgaier
2

Obie zmienne są statyczne - zachowują się przez całą sesję wykonania. Globalny jest widoczny dla każdej funkcji, jeśli deklaruje - nie definiuje - globalny lub jeśli funkcja jest zgodna z definicją w tej samej jednostce kompilacji (plik + zawiera).

Przeniesienie definicji funkcji countdo wewnątrz ogranicza zarówno jej widoczność do najbliższego zbioru {}esów, jak i daje jej czas życia wywołania funkcji (jest tworzony i niszczony, gdy funkcja jest wprowadzana i opuszczana). Zadeklarowanie go staticrównież daje czas życia sesji wykonawczej, który istnieje od początku do końca sesji wykonawczej, utrzymując się między wywołaniami funkcji.

BTW: bądź ostrożny przy używaniu zainicjowanej statyki w obrębie funkcji, ponieważ widziałem, że niektóre wersje kompilatora gnu źle to robią. Automatyczna zmienna z inicjatorem powinna być tworzona i inicjalizowana przy każdym wpisie funkcji. Statyczny z inicjatorem powinien zostać zainicjowany tylko raz, podczas konfiguracji wykonywania, zanim nadana zostanie kontrola main () (podobnie jak globalna). Miałem ponownie zainicjowaną lokalną statykę przy każdym wpisie funkcji, jakby to była automatyka, co jest nieprawidłowe. Przetestuj swój kompilator, aby się upewnić.

JRobert
źródło
Nie jestem pewien, czy rozumiem, co masz na myśli przez funkcję deklarującą globalną. Masz na myśli jako extern?
Peter Bloomfield
@ PeterR.Bloomfield: Nie jestem pewien, o którą część mojego posta pytasz, ale odwoływałem się do dwóch przykładów PO - pierwszej z natury globalnej definicji, a drugiej - lokalnej statystyki.
JRobert
-3

Zgodnie z dokumentacją Atmela: „Jeśli zostanie zadeklarowana zmienna globalna, unikalny adres w pamięci SRAM zostanie przypisany do tej zmiennej w czasie połączenia programu”.

Pełna dokumentacja znajduje się tutaj (Wskazówka nr 2 dotycząca zmiennych globalnych): http://www.atmel.com/images/doc8453.pdf

Void Main
źródło
4
Czy oba przykłady nie kończą się unikalnym adresem w SRAM? Oboje muszą się upierać.
Cybergibbons
2
Tak, tak naprawdę możesz znaleźć te informacje w tym samym dokumencie we
wskazówce