Le variabili globali sono malvagie in Arduino?


24

Sono relativamente nuovo nella programmazione e molte delle migliori pratiche di codifica che sto leggendo affermano effettivamente che ci sono pochissime buone ragioni per usare una variabile globale (o che il codice migliore non ha affatto globali).

Ho fatto del mio meglio per tenerlo a mente, quando scrivo software per creare un'interfaccia Arduino con una scheda SD, parlare con un computer ed eseguire un controller del motore.

Al momento ho 46 globi per circa 1100 righe di codice "livello principiante" (nessuna riga ha più di un'azione). È un buon rapporto o dovrei cercare di ridurlo di più? Inoltre, quali pratiche posso utilizzare per ridurre ulteriormente il numero di globi?

Lo sto chiedendo qui perché mi occupo in particolare delle migliori pratiche per la codifica dei prodotti Arduino piuttosto che della programmazione per computer in generale.


2
In Arduino non puoi evitare le variabili globali. Ogni dichiarazione di variabile al di fuori dell'ambito di una funzione / metodo è globale. Pertanto, se è necessario condividere valori tra funzioni, devono essere globali, a meno che non si desideri passare ogni valore come argomento.

16
@LookAlterno Err, non puoi scrivere classi in Ardunio, dal momento che è solo C ++ con macro e librerie strane? In tal caso, non tutte le variabili sono globali. E anche in C, di solito è considerata la migliore pratica preferire il passaggio di variabili (forse all'interno di strutture) in funzioni piuttosto che avere variabili globali. Potrebbe essere meno conveniente per un piccolo programma, ma di solito paga quando il programma diventa più grande e più complesso.
Muzer,

11
@LookAlterno: "Evito" e "non puoi" sono cose molto diverse.
Corse di leggerezza con Monica l'

2
In realtà, alcuni programmatori incorporati vietano le variabili locali e richiedono invece variabili globali (o variabili statiche con ambito di funzione). Quando i programmi sono piccoli, può essere più semplice analizzarlo come una semplice macchina a stati; perché le variabili non si sovrascrivono mai l'una con l'altra (es .: come fanno le variabili allocate in pila e in pila).
Rob,

1
Le variabili statiche e globali offrono il vantaggio di conoscere il consumo di memoria in fase di compilazione. Con un arduino con memoria molto limitata disponibile, questo può essere un vantaggio. È abbastanza facile per un principiante esaurire la memoria disponibile e sperimentare guasti non rintracciabili.
antipattern

Risposte:


33

Non sono di per sé cattivi , ma tendono a essere abusati laddove non ci siano buone ragioni per usarli.

Ci sono molte volte in cui le variabili globali sono vantaggiose. Soprattutto perché la programmazione di un Arduino è, sotto il cofano, molto diversa dalla programmazione di un PC.

Il più grande vantaggio per le variabili globali è l'allocazione statica. Soprattutto con variabili grandi e complesse come le istanze di classe. L'allocazione dinamica (l'uso di newecc.) È disapprovata a causa della mancanza di risorse.

Inoltre, non si ottiene un singolo albero di chiamate come in un normale programma C (singolo main() funzione che chiama altre funzioni) - invece si ottengono effettivamente due alberi separati ( setup()funzioni di chiamata, quindi loop()funzioni di chiamata), il che significa che a volte le variabili globali sono le l'unico modo per raggiungere il tuo obiettivo (cioè se vuoi usarlo in entrambi setup()e loop()).

Quindi no, non sono cattivi e su un Arduino hanno usi migliori e migliori che su un PC.


Ok, cosa succede se è qualcosa che sto usando solo loop()(o in più funzioni richiamate loop())? sarebbe meglio configurarli in un modo diverso rispetto a definirli all'inizio?
ATE-ENGE,

1
In tal caso probabilmente li definirei in loop () (forse come staticse avessi bisogno di loro per conservare il loro valore attraverso le iterazioni) e passarli attraverso i parametri della funzione nella catena di chiamate.
Majenko

2
Questo è un modo, o void foo(int &var) { var = 4; }foo(n);n
passalo

1
Le classi possono essere allocate in pila. Certo, non è bene mettere in pila grandi istanze, ma comunque.
JAB

1
@JAB Qualsiasi cosa può essere allocata in pila.
Majenko

18

È molto difficile dare una risposta definitiva senza vedere il tuo codice reale.

Le variabili globali non sono malvagie e spesso hanno senso in un ambiente incorporato in cui in genere si accede molto all'hardware. Hai solo quattro UART, una sola porta I2C, ecc. Quindi ha senso usare i globuli per variabili legate a risorse hardware specifiche. E in effetti, la libreria di base Arduino fa che: Serial,Serial1 , ecc, sono le variabili globali. Inoltre, una variabile che rappresenta lo stato globale del programma è in genere globale.

Al momento ho 46 globi per circa 1100 righe di [codice]. È un buon rapporto [...]

Non riguarda i numeri. La domanda giusta che dovresti porti è, per ciascuno di questi globi, se ha senso avere una portata globale.

Tuttavia, 46 globale mi sembra un po 'alto. Se alcuni di questi hanno valori costanti, qualificali come const: il compilatore di solito ottimizzerà la loro memorizzazione. Se una di queste variabili viene utilizzata solo all'interno di una singola funzione, rendila locale. Se si desidera che il suo valore persista tra le chiamate alla funzione, qualificarlo come static. Puoi anche ridurre il numero di globi "visibili" raggruppando le variabili all'interno di una classe e disponendo di un'istanza globale di questa classe. Ma fallo solo quando ha senso mettere insieme le cose. Creare una grande GlobalStuffclasse per avere solo una variabile globale non aiuterà a rendere più chiaro il codice.


1
Ok! Non sapevo staticse ho una variabile che viene utilizzata solo in una funzione, ma ottengo un nuovo valore ogni volta che viene chiamata una funzione (come var=millis()), dovrei farlo static?
ATE-ENGE,

3
No. staticè solo per quando è necessario mantenere il valore. Se la prima cosa da fare nella funzione è impostare il valore della variabile sull'ora corrente, non è necessario mantenere il vecchio valore. Tutto ciò che fa statico in quella situazione è sprecare memoria.
Andrew,

Questa è una risposta molto completa, che esprime entrambi i punti a favore e lo scetticismo nei confronti dei globi. +1
underscore_d

6

Il problema principale con le variabili globali è la manutenzione del codice. Quando si legge una riga di codice, è facile trovare la dichiarazione delle variabili passate come parametro o dichiarate localmente. Non è così facile trovare una dichiarazione di variabili globali (spesso richiede e IDE).

Quando hai molte variabili globali (40 è già molto), diventa difficile avere un nome esplicito che non sia troppo lungo. L'uso dello spazio dei nomi è un modo per chiarire il ruolo delle variabili globali.

Un modo scadente per imitare gli spazi dei nomi in C è:

static struct {
    int motor1, motor2;
    bool sensor;
} arm;

Sul processore Intel o Arm, l'accesso alle variabili globali è più lento rispetto ad altre variabili. Probabilmente è il contrario su Arduino.


2
Su AVR, i globali sono su RAM, mentre la maggior parte dei locali non statici sono allocati ai registri della CPU, il che rende il loro accesso più veloce.
Edgar Bonet,

1
Il problema non è proprio trovare la dichiarazione; essere in grado di comprendere rapidamente il codice è importante ma alla fine secondario rispetto a quello che fa - e lì il vero problema è trovare quale varmint in cui parte sconosciuta del tuo codice è in grado di usare la dichiarazione globale per fare cose strane con un oggetto che hai lasciato fuori all'aperto affinché tutti possano fare ciò che vogliono.
underscore_d

5

Anche se non li userei durante la programmazione per PC, per Arduino hanno alcuni vantaggi. Soprattutto se è già stato detto:

  • Nessun utilizzo dinamico della memoria (creazione di spazi vuoti nello spazio heap limitato di un Arduino)
  • Disponibile ovunque, quindi non è necessario passarli come argomenti (che costa spazio nello stack)

Inoltre, in alcuni casi, soprattutto dal punto di vista delle prestazioni, può essere utile utilizzare variabili globali, anziché creare elementi quando necessario:

  • Per ridurre gli spazi vuoti nello spazio heap
  • L'allocazione e / o la liberazione dinamica della memoria può richiedere molto tempo
  • Le variabili possono essere "riutilizzate", come un elenco di elementi di un elenco globale che vengono utilizzati per molteplici motivi, ad esempio una volta come buffer per la SD e successivamente come buffer di stringa temporaneo.

5

Come per ogni cosa (tranne le goto che sono veramente malvagie) i globi hanno il loro posto.

Ad esempio, se si dispone di una porta seriale di debug o di un file di registro che è necessario poter scrivere da qualsiasi luogo, spesso ha senso renderlo globale. Allo stesso modo, se si dispone di alcune informazioni critiche sullo stato del sistema, renderle globali è spesso la soluzione più semplice. Non ha senso avere un valore che devi passare a ogni singola funzione nel programma.

Come altri hanno già detto 46 sembra molto per poco più di 1000 righe di codice, ma senza conoscere i dettagli di ciò che stai facendo è difficile dire se li usi o meno.

Tuttavia, per ogni globale, poniti alcune domande importanti:

Il nome è chiaro e specifico in modo da non tentare accidentalmente di utilizzare lo stesso nome da qualche altra parte? In caso contrario, cambiare il nome.

Questo deve mai cambiare? In caso contrario, considera di renderlo una const.

Questo deve essere visibile ovunque o è solo globale in modo che il valore venga mantenuto tra le chiamate di funzione? È quindi consigliabile renderlo locale per la funzione e utilizzare la parola chiave static.

Le cose andranno davvero male se questo viene cambiato da un pezzo di codice quando non sto attento? ad esempio se hai due variabili correlate, ad esempio nome e numero ID, che sono globali (vedi la nota precedente sull'uso globale quando hai bisogno di alcune informazioni quasi ovunque), se una di esse viene cambiata senza le altre brutte cose potrebbero accadere. Sì, potresti solo stare attento, ma a volte è bene imporre un po 'di attenzione. ad esempio inserirli in un altro file .c e quindi definire le funzioni che impostano entrambi contemporaneamente e consentono di leggerli. Quindi includi solo le funzioni nel file di intestazione associato, in questo modo il resto del tuo codice può accedere alle variabili solo attraverso le funzioni definite e quindi non può fare confusione.

- aggiornamento - Mi sono appena reso conto che avevi chiesto informazioni sulle migliori pratiche specifiche di Arduino piuttosto che sulla codifica generale e questa è più una risposta di codifica generale. Ma onestamente non c'è molta differenza, la buona pratica è buona pratica. La struttura startup()e loop()di Arduino significa che devi usare i globuli un po 'più delle altre piattaforme in alcune situazioni ma ciò non cambia molto, finisci sempre per mirare al meglio che puoi fare entro i limiti della piattaforma, non importa quale la piattaforma è.


Non so nulla di Arduinos ma faccio molto sviluppo di desktop e server. Esiste un utilizzo accettabile (IMHO) per se gotoè quello di uscire dai loop nidificati, è molto più pulito e più facile da capire rispetto alle alternative.
Persistenza,

Se pensi gotoche siano cattivi, controlla il codice Linux. Probabilmente, sono malvagi quanto i try...catchblocchi se usati come tali.
Dmitry Grigoryev il

5

Sono cattivi? Può essere. Il problema con i globali è che possono essere accessibili e modificati in qualsiasi momento nel tempo da qualsiasi funzione o pezzo di codice in esecuzione, senza restrizioni. Ciò può portare a situazioni, diciamo, difficili da risalire e spiegare. È quindi auspicabile ridurre al minimo la quantità di globuli, se possibile riportare la quantità a zero.

Possono essere evitati? Quasi sempre si. Il problema con Arduino è che ti costringono a questo approccio a due funzioni in cui assumono te setup()e te loop(). In questo caso particolare non hai accesso all'ambito della funzione chiamante di queste due funzioni (probabilmentemain() ). Se lo avessi, potresti sbarazzarti di tutti i globi e utilizzare invece i locali.

Immagina quanto segue:

int main() {
  setup();

  while (true) {
    loop();
  }
  return 0;
}

Questo è probabilmente più o meno l'aspetto della funzione principale di un programma Arduino. Le variabili necessari sia nella setup()e la loop()funzione di sarebbe poi preferibilmente essere dichiarate all'interno del campo di applicazione della main()funzione piuttosto che la portata globale. Potrebbero quindi essere resi accessibili alle altre due funzioni mediante il loro passaggio come argomenti (usando i puntatori se necessario).

Per esempio:

int main() {
  int myVariable = 0;
  setup(&myVariable);

  while (true) {
    loop(&myVariable);
  }
  return 0;
}

Si noti che in questo caso è necessario modificare anche la firma di entrambe le funzioni.

Poiché ciò potrebbe non essere fattibile o desiderabile, vedo davvero solo un modo per rimuovere la maggior parte dei globuli da un programma Arduino senza modificare la struttura del programma forzato.

Se ricordo bene, sei perfettamente in grado di usare C ++ durante la programmazione per Arduino, piuttosto che C. Se non hai familiarità (ancora) con OOP (Object Oriented Programming) o C ++, potrebbe volerci un po 'di tempo per abituarti e alcuni lettura.

La mia proposta sarebbe quella di creare una classe Program e creare un'unica istanza globale di questa classe. Una classe dovrebbe essere considerata il progetto per gli oggetti.

Considera il seguente programma di esempio:

class Program {
public:      
  Program();

  void setup();
  void loop();

private:
  int myFirstSampleVariable;
  int mySecondSampleVariable;
};

Program::Program() :
  myFirstSampleVariable(0),
  mySecondSampleVariable(0)
{

}

void Program::setup() {
  // your setup code goes here
}

void Program::loop() {
  // your loop code goes here
}

Program program; // your single global

void setup() {
  program.setup();
}

void loop() {
  program.loop();
}

Voilà, ci siamo sbarazzati di quasi tutti i globi. Le funzioni in cui inizieresti ad aggiungere la logica dell'applicazione sarebbero le funzioni Program::setup()e Program::loop(). Queste funzioni hanno accesso alle variabili membro specifiche dell'istanza myFirstSampleVariablee mySecondSampleVariablementre le funzioni tradizionali setup()e loop()non hanno accesso in quanto queste variabili sono state contrassegnate come private. Questo concetto si chiama incapsulamento dei dati o occultamento dei dati.

Insegnarti a OOP e / o C ++ è un po 'fuori dalla portata della risposta a questa domanda, quindi mi fermo qui.

Riassumendo: i globi dovrebbero essere evitati ed è quasi sempre possibile ridurre drasticamente la quantità di globi. Anche quando stai programmando per Arduino.

Soprattutto spero che la mia risposta ti sia in qualche modo utile :)


Se preferisci, puoi definire il tuo main () nello schizzo. Ecco come appare uno stock: github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/…
per1234

Un singleton è effettivamente un globale, appena vestito in un modo ancora più confuso. Ha gli stessi aspetti negativi.
patstew,

@patstew Ti dispiace spiegarmi come pensi che abbia gli stessi aspetti negativi? A mio avviso, non è possibile poiché puoi utilizzare l'incapsulamento dei dati a tuo vantaggio.
Arjen,

@ per1234 Grazie! Non sono assolutamente un esperto di Arduino, ma suppongo che anche il mio primo suggerimento potrebbe funzionare.
Arjen,

2
Bene, è ancora lo stato globale a cui è possibile accedere ovunque nel programma, basta accedervi Program::instance().setup()invece di globalProgram.setup(). Inserire variabili globali correlate in una classe / struttura / spazio dei nomi può essere utile, specialmente se sono necessarie solo per un paio di funzioni correlate, ma il modello singleton non aggiunge nulla. In altre parole static Program p;ha una memoria globale e static Program& instance()un accesso globale, che equivale allo stesso modo della semplice Program globalProgram;.
patstew,

4

Le variabili globali non sono mai cattive . Una regola generale contro di loro è solo una stampella per farti sopravvivere abbastanza a lungo da acquisire l'esperienza per prendere decisioni migliori.

Che cosa sia una variabile globale, è un'ipotesi inerente che esiste solo una cosa (non importa se stiamo parlando di un array o una mappa globale che potrebbe contenere più cose, che contiene ancora l'assunto che ci sia solo uno di questi elenchi o mappature, e non più indipendenti).

Quindi, prima di fare uso di un globale, ti devi chiedere: è concepibile che io abbia mai voglia di usare più di una di queste cose? Se risulta essere vero lungo la linea, dovrai modificare il codice per non globalizzare quella cosa e probabilmente scoprirai lungo il modo in cui altre parti del tuo codice dipendono da quell'assunto di unicità, quindi tu Dovremo anche sistemarli e il processo diventa noioso e soggetto a errori. "Non usare i globi" viene insegnato perché di solito è un costo piuttosto piccolo per evitare i globuli sin dall'inizio, ed evita il potenziale di dover pagare un grande costo in seguito.

Ma le ipotesi di semplificazione che consentono i globali rendono anche il tuo codice più piccolo, più veloce e usano meno memoria, perché non deve passare l'idea di quale cosa sta usando, non deve fare indiretta, non deve considera la possibilità che la cosa desiderata possa non esistere, ecc. In Embedded hai maggiori probabilità di essere vincolato per dimensioni del codice e / o tempo e / o memoria della CPU rispetto a un PC, quindi questi risparmi possono avere importanza. E molte applicazioni integrate hanno anche una maggiore rigidità nei requisiti: sai che il tuo chip ha solo una di una determinata periferica, l'utente non può semplicemente collegarne un'altra a una porta USB o qualcosa del genere.

Un altro motivo comune per volere più di qualcosa che sembra unico è il test: testare l'interazione tra due componenti è più semplice quando si può semplicemente passare un'istanza di test di un componente a una funzione, mentre cercare di modificare il comportamento di un componente globale è una proposta più complicata. Ma i test nel mondo embedded tendono ad essere molto diversi da qualsiasi altra parte, quindi questo potrebbe non essere applicabile a te. Per quanto ne so, Arduino non ha alcuna cultura test.

Quindi vai avanti e usa i globi quando sembrano utili. La polizia del codice non verrà a prenderti. Sappi solo che la scelta sbagliata potrebbe portare a molto più lavoro per te lungo la strada, quindi se non sei sicuro ...


0

Le variabili globali sono malvagie in Arduino?

nulla è intrinsecamente malvagio, comprese le variabili globali. Lo definirei come un "male necessario" - può rendere la tua vita molto più semplice ma dovrebbe essere affrontato con cautela.

Inoltre, quali pratiche posso utilizzare per ridurre ulteriormente il numero di globi?

utilizzare le funzioni wrapper per accedere alle variabili globali. quindi almeno lo gestisci dal punto di vista dell'ambito.


3
Se usi le funzioni wrapper per accedere alle variabili globali, puoi anche inserire le tue variabili all'interno di queste funzioni.
Dmitry Grigoryev il
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.