Zastosowanie zmiennych globalnych w systemach wbudowanych

17

Zacząłem pisać oprogramowanie układowe dla mojego produktu i jestem tutaj debiutantem. Przejrzałem wiele artykułów na temat niestosowania zmiennych globalnych lub funkcji. Czy jest jakiś limit używania zmiennych globalnych w systemie 8-bitowym, czy też jest to kompletne „Nie-Nie”. Jak powinienem używać zmiennych globalnych w moim systemie, czy powinienem ich całkowicie unikać?

Chciałbym skorzystać z cennych rad od was na ten temat, aby moje oprogramowanie układowe było bardziej kompaktowe.

Świeżak91
źródło
To pytanie nie dotyczy wyłącznie systemów wbudowanych. Duplikat można znaleźć tutaj .
Lundin
@Lundin Z Twojego linku: „W dzisiejszych czasach ma to znaczenie tylko w środowiskach osadzonych, w których pamięć jest dość ograniczona. Coś, co trzeba wiedzieć przed założeniem, że osadzone jest takie samo jak inne środowiska i założyć, że zasady programowania są takie same”.
endolith,
Zakres staticpliku @endolith to nie to samo, co „globalny”, patrz moja odpowiedź poniżej.
Lundin

Odpowiedzi:

31

Z powodzeniem możesz używać zmiennych globalnych, o ile pamiętasz wytyczne @ Phila. Oto kilka dobrych sposobów na uniknięcie problemów bez zmniejszania kompilacji kodu.

  1. Użyj lokalnych zmiennych statycznych dla trwałego stanu, do którego chcesz uzyskać dostęp tylko w obrębie jednej funkcji.

    #include <stdint.h>
    void skipper()
    {
        static uint8_t skip_initial_cycles = 5;
        if (skip_initial_cycles > 0) {
            skip_initial_cycles -= 1;
            return;
        }
        /* ... */
    }
  2. Użyj struct, aby utrzymać powiązane zmienne razem, aby było wyraźniej, gdzie powinny być używane, a gdzie nie.

    struct machine_state {
         uint8_t level;
         uint8_t error_code;
    } machine_state;
    
    struct led_state {
        uint8_t red;
        uint8_t green;
        uint8_t blue;
    } led_state;
    
    void machine_change_state()
    {
        machine_state.level += 1;
        /* ... */
        /* We can easily remember not to use led_state in this function. */
    }
    
    void machine_set_io()
    {
        switch (machine_state.level) {
        case 1:
            PIN_MACHINE_IO_A = 1;
            /* ... */
        }
    }
  3. Użyj globalnych zmiennych statycznych, aby zmienne były widoczne tylko w bieżącym pliku C. Zapobiega to przypadkowemu dostępowi do kodu w innych plikach z powodu konfliktów nazw.

    /* time_machine.c */
    static uint8_t current_time;
    /* ... */
    
    /* delay.c */
    static uint8_t current_time; /* A completely separate variable for this C file only. */
    /* ... */

Na koniec, jeśli modyfikujesz zmienną globalną w ramach procedury przerwania i czytasz ją w innym miejscu:

  • Zaznacz zmienną volatile.
  • Upewnij się, że jest atomowy dla procesora (tj. 8-bitowy dla 8-bitowego procesora).

LUB

  • Użyj mechanizmu blokującego, aby zabezpieczyć dostęp do zmiennej.
Richarddonkin
źródło
zmienne i / lub atomowe zmienne nie pomogą ci uniknąć błędów, potrzebujesz jakiegoś rodzaju blokady / semafora lub krótkotrwałego maskowania przerwań podczas zapisu do zmiennej.
John U
3
To dość wąska definicja „działa dobrze”. Chodziło mi o to, że deklarowanie czegoś niestabilnego nie zapobiega konfliktom. Ponadto twój trzeci przykład nie jest świetnym pomysłem - posiadanie dwóch oddzielnych globałów o tej samej nazwie co najmniej utrudnia zrozumienie / utrzymanie kodu.
John U
1
@JohnU Nie powinieneś używać lotnych, aby zapobiec warunkom wyścigowym, w rzeczywistości to nie pomoże. Powinieneś używać lotnego, aby zapobiec niebezpiecznym błędom optymalizacji kompilatora, typowym w kompilatorach systemów wbudowanych.
Lundin
2
@JohnU: Normalnym użyciem volatilezmiennych jest umożliwienie działania kodu w jednym kontekście wykonania, aby kod w innym kontekście wykonania mógł wiedzieć, że coś się wydarzyło. W systemie 8-bitowym bufor, który ma pomieścić potęgę dwóch bajtów nie większą niż 128, może być zarządzany jednym lotnym bajtem wskazującym całkowitą liczbę bajtów włożoną do bufora (mod 256) i inny oznacza liczbę wyciągniętych bajtów w ciągu całego życia, pod warunkiem, że tylko jeden kontekst wykonania umieszcza dane w buforze, a tylko jeden z nich.
supercat
2
@JohnU: Chociaż możliwe jest użycie jakiejś formy blokowania lub tymczasowego wyłączenia przerwań w celu zarządzania buforem, tak naprawdę nie jest to konieczne ani pomocne. Gdyby bufor musiał pomieścić 128-255 bajtów, kodowanie musiałoby się nieco zmienić, a gdyby musiał pomieścić więcej, prawdopodobnie konieczne byłoby wyłączenie przerwań, ale w systemie 8-bitowym bufory mogą być małe; systemy z większymi buforami mogą generalnie wykonywać zapisy atomowe większe niż 8 bitów.
supercat
24

Powody, dla których nie chcesz używać zmiennych globalnych w systemie 8-bitowym są takie same, jak nie chcesz ich używać w żadnym innym systemie: utrudniają one rozumowanie na temat zachowania programu.

Tylko źli programiści rozłączają się na zasadach takich jak „nie używaj zmiennych globalnych”. Dobrzy programiści rozumieją powód reguł, a następnie traktują je bardziej jak wytyczne.

Czy twój program jest łatwy do zrozumienia? Czy jego zachowanie jest przewidywalne? Czy łatwo jest modyfikować jego części bez zepsucia innych części? Jeśli odpowiedź na każde z tych pytań brzmi „ tak” , to jesteś na dobrej drodze do dobrego programu.

Phil Frost
źródło
1
Co powiedział @MichaelKaras - ważne jest zrozumienie, co oznaczają te rzeczy i jak na nie wpływają (i jak mogą cię ugryźć).
John U
5

Nie powinieneś całkowicie unikać używania zmiennych globalnych (w skrócie „globals”). Ale powinieneś ich używać rozsądnie. Praktyczne problemy z nadmiernym użyciem globali:

  • Globale są widoczne w całej jednostce kompilacji. Każdy kod w jednostce kompilacyjnej może modyfikować globalny. Konsekwencje modyfikacji mogą pojawić się wszędzie, gdzie jest oceniana ta globalność.
  • W rezultacie globale sprawiają, że kod jest trudniejszy do odczytania i zrozumienia. Programista zawsze musi pamiętać o wszystkich miejscach, w których globalny jest oceniany i przypisywany.
  • Nadmierne użycie globałów powoduje, że kod jest bardziej podatny na defekty.

Dobrą praktyką jest dodawanie przedrostka g_do nazwy zmiennych globalnych. Na przykład g_iFlags. Kiedy zobaczysz zmienną z prefiksem w kodzie, natychmiast rozpoznasz, że jest to zmienna globalna.

Nick Alexeev
źródło
2
Flaga nie musi być globalna. ISR może na przykład wywołać funkcję, która ma zmienną statyczną.
Phil Frost
+1 Nie słyszałem wcześniej o takiej sztuczce. Usunąłem ten akapit z odpowiedzi. W jaki sposób staticflaga stałaby się widoczna dla main()? Czy sugerujesz, że ta sama funkcja, która ma tę funkcję, staticmoże przywrócić ją main()później?
Nick Alexeev
To jeden ze sposobów, aby to zrobić. Być może funkcja przyjmuje nowy stan i zwraca stary stan. Istnieje wiele innych sposobów. Być może masz jeden plik źródłowy z funkcją ustawiania flagi, a drugi do jej uzyskania, ze statyczną zmienną globalną zawierającą stan flagi. Chociaż technicznie jest to termin „globalny” według terminologii C, jego zakres jest ograniczony tylko do tego pliku, który zawiera tylko funkcje, które należy znać. Oznacza to, że jego zakres nie jest „globalny”, co naprawdę stanowi problem. C ++ zapewnia dodatkowe mechanizmy enkapsulacji.
Phil Frost
4
Niektóre metody unikania globalizacji (takie jak dostęp do sprzętu tylko za pośrednictwem sterowników urządzeń) mogą być zbyt nieefektywne w przypadku środowiska 8-bitowego, w którym brakuje zasobów.
Spehro Pefhany
1
@SpehroPefhany Dobrzy programiści rozumieją powód reguł, a następnie traktują je bardziej jak wytyczne. Gdy wytyczne są sprzeczne, dobry programista dokładnie waży wagę.
Phil Frost
4

Zaletą globalnych struktur danych w pracy osadzonej jest to, że są one statyczne. Jeśli każda potrzebna zmienna ma charakter globalny, nigdy nie zabraknie ci pamięci po wprowadzeniu funkcji i zwolnieniu miejsca na stosie. Ale w takim razie po co funkcje? Dlaczego nie jedna duża funkcja, która obsługuje całą logikę i procesy - jak program BASIC bez GOSUB-a. Jeśli posuniesz ten pomysł wystarczająco daleko, będziesz miał typowy program języka asemblera z lat siedemdziesiątych. Wydajne i niemożliwe do utrzymania i rozwiązywania problemów.

Używaj więc globalnie rozsądnie, takich jak zmienne stanu (na przykład, jeśli każda funkcja musi wiedzieć, czy system jest w stanie interpretacji lub uruchomienia) i inne struktury danych, które muszą być widoczne przez wiele funkcji i jak mówi @PhilFrost, zachowanie twoje funkcje są przewidywalne? Czy istnieje możliwość wypełnienia stosu ciągiem wejściowym, który nigdy się nie kończy? Są to kwestie dotyczące projektowania algorytmów.

Zauważ, że statyczny ma inne znaczenie wewnątrz i na zewnątrz funkcji. /programming/5868947/difference-between-static-variable-inside-and-outside-of-a-function

/programming/5033627/static-variable-inside-of-a-function-in-c

C. Towne Springer
źródło
1
Wiele kompilatorów systemów wbudowanych przypisuje zmienne automatyczne statycznie, ale nakłada zmienne używane przez funkcje, które nie mogą być objęte zakresem jednocześnie; generalnie daje to wykorzystanie pamięci, które jest równe najgorszemu możliwemu wykorzystaniu dla systemu opartego na stosie, w którym mogą rzeczywiście występować wszystkie statycznie możliwe sekwencje wywołań.
supercat
4

Zmienne globalne powinny być używane tylko dla prawdziwie globalnego stanu. Użycie zmiennej globalnej do przedstawienia czegoś w rodzaju np. Szerokości geograficznej północnej granicy mapy będzie działać tylko wtedy, gdy będzie tylko jedna „północna granica mapy”. Jeśli w przyszłości kod będzie musiał działać z wieloma mapami, które mają różne granice północy, kod, który używa zmiennej globalnej dla granicy północnej, prawdopodobnie będzie musiał zostać przerobiony.

W typowych aplikacjach komputerowych często nie ma szczególnego powodu, aby zakładać, że nigdy nie będzie więcej niż jednego z nich. Jednak w systemach wbudowanych takie założenia są często znacznie bardziej uzasadnione. Chociaż możliwe jest, że typowy program komputerowy może zostać wezwany do obsługi wielu jednoczesnych użytkowników, interfejs użytkownika typowego systemu osadzonego zostanie zaprojektowany do działania przez jednego użytkownika wchodzącego w interakcję z jego przyciskami i wyświetlaczem. Jako taki będzie miał w dowolnym momencie pojedynczy stan interfejsu użytkownika. Zaprojektowanie systemu w taki sposób, aby wielu użytkowników mogło wchodzić w interakcje z wieloma klawiaturami i wyświetlaczami, wymagałoby znacznie większej złożoności i jego wdrożenie zajęłoby znacznie więcej czasu niż zaprojektowanie go dla jednego użytkownika. Jeśli system nigdy nie jest wzywany do obsługi wielu użytkowników, wszelkie dodatkowe wysiłki włożone w ułatwienie takiego użytkowania zostaną zmarnowane. O ile nie jest prawdopodobne, że wymagana będzie obsługa wielu użytkowników, rozsądniej byłoby zaryzykować konieczność odrzucenia kodu używanego dla interfejsu jednego użytkownika w przypadku, gdy wymagana jest obsługa wielu użytkowników, niż poświęcenie dodatkowego czasu na dodawanie wielu wsparcie użytkownika, które prawdopodobnie nigdy nie będzie potrzebne.

Powiązanym czynnikiem w przypadku systemów osadzonych jest to, że w wielu przypadkach (szczególnie z udziałem interfejsów użytkownika) jedynym praktycznym sposobem obsługi więcej niż jednego z nich byłoby użycie wielu wątków. W przypadku braku innej potrzeby wielowątkowości, prawdopodobnie lepiej jest zastosować prostą konstrukcję jednowątkową niż zwiększyć złożoność systemu dzięki wielowątkowości, która prawdopodobnie nigdy nie będzie naprawdę konieczna. Jeśli dodanie więcej niż jednego z elementów wymagałoby i tak ogromnego przeprojektowania systemu, nie będzie miało znaczenia, czy wymaga to również przerobienia użycia niektórych zmiennych globalnych.

supercat
źródło
Tak więc utrzymywanie zmiennych globalnych, które nie będą ze sobą kolidować, nie będzie problemem. np .: Day_cntr, week_cntr itp. do zliczania odpowiednio dni i tygodni. I ufam, że nie należy celowo używać wielu zmiennych globalnych, które przypominają ten sam cel i należy je jasno zdefiniować. Bardzo dziękuję za przytłaczającą odpowiedź. :)
Rookie91
-1

Wiele osób ma wątpliwości co do tego tematu. Definicja zmiennej globalnej to:

Coś, co jest dostępne z dowolnego miejsca w twoim programie.

To nie to samo, co zmienne zakresu pliku , które są deklarowane przez słowo kluczowe static. To nie są zmienne globalne, to lokalne zmienne prywatne.

 int x; // global variable
 static int y; // file scope variable

 void some_func (void) {...} // added to demonstrate that the variables above are at file scope.

Czy powinieneś używać zmiennych globalnych? Jest kilka przypadków, w których jest w porządku:

W każdym innym przypadku nie wolno nigdy używać zmiennych globalnych. Nigdy nie ma ku temu powodu. Zamiast tego użyj zmiennych zakresu pliku , co jest całkowicie w porządku.

Powinieneś dążyć do napisania niezależnych, autonomicznych modułów kodu zaprojektowanych do wykonania określonego zadania. Wewnątrz tych modułów wewnętrzne zmienne zakresu plików powinny znajdować się jako prywatne elementy danych. Ta metoda projektowania znana jest jako orientacja obiektowa i powszechnie uznawana za dobrą konstrukcję.

Lundin
źródło
Dlaczego głosowanie negatywne?
m.Alin
2
Myślę, że „zmienna globalna” może być również używana do opisywania przydziałów do segmentu globalnego (nie tekstu, stosu lub stosu). W tym sensie statyczne zmienne statyczne i funkcyjne są / mogą być „globalne”. W kontekście tego pytania jest dość jasne, że globalny odnosi się do segmentu zakresu, a nie alokacji (choć możliwe jest , że PO nie wiedział o tym).
Paul A. Clayton
1
@ PaulA.Clayton Nigdy nie słyszałem o formalnym terminie zwanym „globalnym segmentem pamięci”. Twoje zmienne skończą się w jednym z następujących miejsc: rejestry lub stos (alokacja środowiska wykonawczego), sterty (alokacja środowiska wykonawczego), segment .data (jawnie zainicjowane zmienne pamięci statycznej), segment .bss (zerowane zmienne pamięci statycznej), .rodata (czytaj - tylko stałe) lub .text (część kodu). Jeśli trafią gdzie indziej, jest to ustawienie specyficzne dla projektu.
Lundin
1
@ PaulA.Clayton Podejrzewam, że to, co nazywacie „segmentem globalnym”, jest .datasegmentem.
Lundin