Uso di variabili globali nei sistemi integrati


17

Ho iniziato a scrivere firmware per il mio prodotto e sono un novellino qui. Ho esaminato molti articoli sul non utilizzo di variabili o funzioni globali. Esiste un limite per l'utilizzo di variabili globali in un sistema a 8 bit o è un "No-No" completo. Come dovrei usare le variabili globali nel mio sistema o dovrei evitarle completamente?

Vorrei ricevere preziosi consigli da voi ragazzi su questo argomento per rendere il mio firmware più compatto.


Questa domanda non è unica per i sistemi integrati. Un duplicato può essere trovato qui .
Lundin,

@Lundin Dal tuo link: "Al giorno d'oggi ciò conta solo negli ambienti embedded in cui la memoria è piuttosto limitata. Qualcosa da sapere prima di assumere che l'embedded sia uguale agli altri ambienti e assumere che le regole di programmazione siano le stesse su tutta la linea".
Endolith,

L' staticambito del file @endolith non è la stessa cosa di "globale", vedere la mia risposta di seguito.
Lundin,

Risposte:


31

Puoi utilizzare le variabili globali con successo, purché tieni a mente le linee guida di @ Phil. Tuttavia, ecco alcuni modi interessanti per evitare i loro problemi senza rendere il codice compilato meno compatto.

  1. Utilizzare le variabili statiche locali per lo stato persistente a cui si desidera accedere solo all'interno di una funzione.

    #include <stdint.h>
    void skipper()
    {
        static uint8_t skip_initial_cycles = 5;
        if (skip_initial_cycles > 0) {
            skip_initial_cycles -= 1;
            return;
        }
        /* ... */
    }
  2. Utilizzare uno struct per mantenere insieme le variabili correlate, per rendere più chiaro dove dovrebbero essere utilizzate e dove no.

    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. Utilizzare le variabili statiche globali per rendere visibili le variabili solo all'interno del file C. Ciò impedisce l'accesso accidentale da parte del codice in altri file a causa di conflitti di denominazione.

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

Come nota finale, se stai modificando una variabile globale all'interno di una routine di interrupt e la stai leggendo altrove:

  • Segna la variabile volatile.
  • Assicurarsi che sia atomico per la CPU (ovvero 8 bit per una CPU a 8 bit).

O

  • Utilizzare un meccanismo di blocco per proteggere l'accesso alla variabile.

varianti volatili e / o atomici non ti aiuteranno a evitare errori, hai bisogno di una sorta di blocco / semaforo o di mascherare brevemente gli interrupt quando scrivi sulla variabile.
John U,

3
Questa è una definizione piuttosto ristretta di "lavorare bene". Il mio punto era che dichiarare qualcosa di volatile non impedisce i conflitti. Inoltre, il tuo terzo esempio non è una grande idea: avere due globi separati con lo stesso nome sta almeno rendendo il codice più difficile da capire / mantenere.
John U,

1
@JohnU Non dovresti usare la volatilità per prevenire le condizioni di gara, anzi ciò non aiuterà. Si dovrebbe usare volatile per prevenire pericolosi bug di ottimizzazione del compilatore comuni nei compilatori di sistemi integrati.
Lundin,

2
@JohnU: L'uso normale delle volatilevariabili è consentire al codice in esecuzione in un contesto di esecuzione di far sapere al codice in un altro contesto di esecuzione che è successo qualcosa. Su un sistema a 8 bit, un buffer che può contenere un numero di byte di potenza pari a due non superiore a 128 può essere gestito con un byte volatile che indica il numero di vita totale dei byte inseriti nel buffer (mod 256) e un altro che indica il numero di byte di durata estratto, a condizione che solo un contesto di esecuzione inserisca i dati nel buffer e solo uno ne rimuova i dati.
supercat

2
@JohnU: Mentre potrebbe essere possibile utilizzare una qualche forma di bloccaggio o temporaneamente interrupt disattivare per gestire il buffer, non è realmente necessario o utile. Se il buffer dovesse contenere 128-255 byte, la codifica dovrebbe cambiare leggermente, e se dovesse contenere più di quello, sarebbe probabilmente necessario disabilitare gli interrupt, ma su un sistema a 8 bit i buffer potrebbero essere piccoli; sistemi con buffer grandi possono generalmente fare scritture atomici superiori a 8 bit.
Supercat

24

Le ragioni che non vorresti usare le variabili globali in un sistema a 8 bit sono gli stessi che non vorresti di utilizzarli in qualsiasi altro sistema: fanno ragionamento sul comportamento del programma difficile.

Solo i programmatori cattivi ottenere appeso su regole come "non usare le variabili globali". I bravi programmatori a capire il motivo dietro le regole, poi trattare le regole più come linee guida.

Il tuo programma è facile da capire? Il suo comportamento è prevedibile? È facile modificarne parti senza romperne altre? Se la risposta a ciascuna di queste domande è , allora siete sulla strada per un buon programma.


1
Quali @MichaelKaras detto - capire che cosa significano queste cose e come influenzano le cose (e come possono mordere voi) è la cosa importante.
John U

5

Non si dovrebbe completamente evitare l'uso di variabili globali ( "globals" in breve). Ma, li si dovrebbe usare con giudizio. I problemi pratici con l'uso eccessivo di variabili globali:

  • Globali sono visibili in tutta l'unità di compilazione. Qualsiasi codice in unità di compilazione può modificare a livello globale. Le conseguenze della modifica può emergere ovunque si valuta questo mondiale.
  • Di conseguenza globali rendere il codice più difficile da leggere e capire. Il programmatore deve sempre tenere a mente tutti i luoghi in cui viene valutata e contrassegnati con il globale.
  • L'uso eccessivo di variabili globali rende il codice più difetti inclini.

È buona norma aggiungere un prefisso g_al nome delle variabili globali. Ad esempio g_iFlags,. Quando vedi la variabile con il prefisso nel codice, riconosci immediatamente che è globale.


2
La bandiera non deve essere globale. L'ISR potrebbe chiamare una funzione che ha una variabile statica, ad esempio.
Phil Frost,

+1 Non ho mai sentito parlare di questo trucco prima. Ho rimosso quel paragrafo dalla risposta. In che modo la staticbandiera diventerebbe visibile al main()? Stai insinuando che la stessa funzione che ha staticpuò restituirla a quella main()successiva?
Nick Alexeev

Questo è un modo per farlo. Forse la funzione richiede il nuovo stato da impostare e restituisce il vecchio stato. Ci sono molti altri modi. Forse hai un file sorgente con una funzione per impostare il flag e un altro per ottenerlo, con una variabile globale statica che contiene lo stato del flag. Anche se tecnicamente si tratta di una terminologia "globale" di C, il suo scopo è limitato a quel solo file, che contiene solo le funzioni che devono conoscere. Cioè, il suo ambito non è "globale", che è davvero il problema. C ++ fornisce ulteriori meccanismi di incapsulamento.
Phil Frost,

4
Alcuni metodi per evitare i globi (come l'accesso all'hardware solo tramite driver di dispositivo) potrebbero essere troppo inefficienti per un ambiente a 8 bit gravemente affamato di risorse.
Spehro Pefhany,

1
@SpehroPefhany I bravi programmatori comprendono il motivo dietro le regole, quindi trattano le regole più come linee guida. Quando le linee guida sono in conflitto, il buon programmatore valuta attentamente la bilancia.
Phil Frost,

4

Il vantaggio delle strutture di dati globali nel lavoro incorporato è che sono statiche. Se ogni variabile di cui hai bisogno è globale, non rimarrai mai accidentalmente a corto di memoria quando le funzioni vengono immesse e lo spazio viene creato per loro nello stack. Ma allora, a quel punto, perché avere funzioni? Perché non una grande funzione che gestisce tutta la logica e i processi - come un programma BASIC senza GOSUB consentito. Se prendi questa idea abbastanza lontano avrai un tipico programma di assemblaggio degli anni '70. Efficiente, impossibile da mantenere e risoluzione dei problemi.

Quindi usa i globi in modo giudizioso, come le variabili di stato (ad esempio, se ogni funzione deve sapere se il sistema è in stato interpretato o eseguito) e altre strutture di dati che devono essere viste da molte funzioni e come dice @PhilFrost, è il comportamento di le tue funzioni sono prevedibili? Esiste la possibilità di riempire lo stack con una stringa di input che non finisce mai? Queste sono questioni per la progettazione di algoritmi.

Si noti che statico ha un significato diverso all'interno e all'esterno di una funzione. /programming/5868947/difference-between-static-variable-inside-and-outside-of-a-function

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


1
Molti compilatori di sistemi embedded assegnano staticamente variabili automatiche, ma sovrappongono variabili utilizzate da funzioni che non possono essere contemporaneamente nell'ambito; questo generalmente produce un utilizzo della memoria che è uguale all'uso peggiore possibile per un sistema basato su stack in cui possono effettivamente verificarsi tutte le sequenze di chiamate staticamente possibili.
supercat

4

Le variabili globali dovrebbero essere utilizzate solo per uno stato veramente globale. L'uso di una variabile globale per rappresentare qualcosa di simile, ad esempio, la latitudine del limite settentrionale della mappa funzionerà solo se ci sarà sempre un solo "limite settentrionale della mappa". Se in futuro il codice potrebbe dover funzionare con più mappe con confini settentrionali diversi, sarà probabilmente necessario rielaborare il codice che utilizza una variabile globale per il confine settentrionale.

Nelle tipiche applicazioni informatiche, spesso non vi è alcun motivo particolare per presumere che non ci sarà mai più di qualcosa di simile. Nei sistemi embedded, tuttavia, tali ipotesi sono spesso molto più ragionevoli. Mentre è possibile che un tipico programma informatico possa essere chiamato a supportare più utenti simultanei, l'interfaccia utente di un tipico sistema incorporato sarà progettata per essere gestita da un singolo utente che interagisce con i suoi pulsanti e display. Come tale, in qualsiasi momento avrà un singolo stato dell'interfaccia utente. Progettare il sistema in modo che più utenti possano interagire con più tastiere e display richiederebbe molta più complessità e richiederebbe molto più tempo per essere implementato, piuttosto che progettarlo per un singolo utente. Se il sistema non è mai chiamato a supportare più utenti, qualsiasi ulteriore sforzo investito per facilitare tale utilizzo sarà sprecato. A meno che non sia probabile che sia richiesto il supporto multiutente, sarebbe probabilmente più saggio rischiare di dover scartare il codice utilizzato per un'interfaccia utente singolo nel caso in cui sia richiesto il supporto multiutente, piuttosto che dedicare tempo extra all'aggiunta di più assistenza agli utenti che probabilmente non sarà mai necessaria.

Un fattore correlato con i sistemi incorporati è che in molti casi (in particolare che coinvolgono le interfacce utente), l'unico modo pratico per supportare avere più di una cosa sarebbe usare più thread. In assenza di qualche altra necessità di multi-threading, è probabilmente meglio utilizzare un semplice design a thread singolo piuttosto che aumentare la complessità del sistema con il multi-thread che probabilmente non sarà mai realmente necessario. Se l'aggiunta di più di qualcosa richiederebbe comunque una riprogettazione del sistema enorme, non importa se richiede anche una rielaborazione dell'uso di alcune variabili globali.


Quindi mantenere variabili globali che non si scontreranno tra loro non sarà un problema giusto. ad esempio: Day_cntr, week_cntr ecc. per contare rispettivamente giorni e settimane. E confido che non si debba usare deliberatamente molte variabili globali che assomigliano allo stesso scopo e devono definirle chiaramente. Grazie per la risposta schiacciante. :)
Rookie91

-1

Molte persone sono confuse su questo argomento. La definizione di una variabile globale è:

Qualcosa che è accessibile da qualsiasi parte del programma.

Questa non è la stessa cosa delle variabili dell'ambito del file , che sono dichiarate dalla parola chiave static. Quelle non sono variabili globali, sono variabili private locali.

 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.

Dovresti usare le variabili globali? Ci sono alcuni casi in cui va bene:

In tutti gli altri casi, non dovrai mai usare variabili globali. Non c'è mai un motivo per farlo. Usa invece le variabili dell'ambito del file , il che va perfettamente bene.

Dovresti cercare di scrivere moduli di codice indipendenti e autonomi progettati per svolgere un'attività specifica. All'interno di tali moduli, le variabili interne dell'ambito del file devono risiedere come membri di dati privati. Questo metodo di progettazione è noto come orientamento agli oggetti e ampiamente riconosciuto come un buon design.


Perché il downvote?
m

2
Penso che la "variabile globale" possa anche essere usata per descrivere le allocazioni a un segmento globale (non testo, stack o heap). In tal senso, i file statici e le variabili statiche di funzione sono / possono essere "globali". Nel contesto di questa domanda, è in qualche modo chiaro che il globale si sta riferendo al settore dell'ambito e non dell'allocazione (sebbene sia possibile che il PO non lo sapesse).
Paul A. Clayton,

1
@ PaulA.Clayton Non ho mai sentito parlare di un termine formale chiamato "segmento di memoria globale". Le variabili finiranno in uno dei seguenti posti: registri o stack (allocazione runtime), heap (allocazione runtime), segmento .data (variabili di memoria statica esplicitamente inizializzate), segmento .bss (variabili di memoria statica azzerate), .rodata (leggi -solo costanti) o .text (parte del codice). Se finiscono altrove, si tratta di un'impostazione specifica per il progetto.
Lundin,

1
@ PaulA.Clayton Sospetto che quello che tu chiami "segmento globale" sia il .datasegmento.
Lundin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.