Quando va bene usare una variabile globale in C?

Apparentemente c‘è parecchia varietà di opinioni là fuori, che vanno da ” Mai! Incapsula sempre (anche se è con una semplice macro!) ” A ” Non è un grosso problema – usali quando è più conveniente che non.

Così.

Ragioni specifiche e concrete (preferibilmente con un esempio)

  • Perché le variabili globali sono pericolose
  • Quando le variabili globali dovrebbero essere utilizzate al posto delle alternative
  • Quali alternative esistono per coloro che sono tentati di usare le variabili globali in modo inappropriato

Mentre questo è soggettivo, sceglierò una risposta (che per me rappresenta al meglio la relazione di amore / odio che ogni sviluppatore dovrebbe avere con i globali) e la comunità voterà la loro appena sotto.

Credo che sia importante per i neofiti avere questo tipo di riferimento, ma per favore non ingombrarlo se esiste un’altra risposta che è sostanzialmente simile alla tua – aggiungi un commento o modifica la risposta di qualcun altro.

-Adamo

Le variabili dovrebbero sempre avere l’ambito più piccolo ansible. L’argomento alla base di ciò è che ogni volta che si aumenta l’ambito si ha più codice che potenzialmente modifica la variabile, quindi nella soluzione viene indotta più complessità.

È quindi chiaro che è preferibile evitare l’uso di variabili globali se la progettazione e l’implementazione lo consentono in modo naturale. Per questo preferisco non utilizzare le variabili globali a meno che non siano realmente necessarie.

Nemmeno io sono d’accordo con l’affermazione “mai”. Come ogni altro concetto, le variabili globali sono uno strumento che dovrebbe essere usato quando necessario. Preferirei usare le variabili globali piuttosto che usare alcuni costrutti artificiali (come i puntatori di passaggio) che maschererebbero solo il vero intento. Buoni esempi in cui le variabili globali sono utilizzate sono implementazioni di pattern singleton o accesso al registro nei sistemi embedded.

Su come effettivamente rilevare usi eccessivi delle variabili globali: ispezione, ispezione, ispezione. Ogni volta che vedo una variabile globale devo chiedermi: è DAVVERO necessario in un ambito globale?

L’unico modo per far funzionare le variabili globali è dare loro nomi che assicurino che sono unici.

Questo nome di solito ha un prefisso associato ad alcuni “moduli” o raccolte di funzioni per le quali la variabile globale è particolarmente focalizzata o significativa.

Ciò significa che la variabile “appartiene” a quelle funzioni – è parte di esse. In effetti, il globale può essere solitamente “avvolto” con una piccola funzione che va di pari passo con le altre funzioni – nello stesso prefisso dello stesso file .h .

Bonus.

Quando lo fai, improvvisamente, non è più globale. Ora fa parte di alcuni moduli di funzioni correlate.

Questo può sempre essere fatto. Con un po ‘di pensiero ogni variabile globale può essere assegnata ad alcune raccolte di funzioni, assegnate a uno specifico file .h e isolate con funzioni che consentono di modificare la variabile senza interrompere nulla.

Piuttosto che dire “non usare mai le variabili globali”, puoi dire “assegna le responsabilità della variabile globale a qualche modulo dove ha più senso”.

Considera questo koan: “se l’ambito è abbastanza ristretto, tutto è globale”.

È ancora molto ansible in questa età aver bisogno di scrivere un programma di utilità molto veloce per fare un lavoro una tantum.

In tali casi, l’energia necessaria per creare un accesso sicuro alle variabili è maggiore dell’energia risparmiata dai problemi di debug in una così piccola utility.

Questo è l’unico caso che riesco a pensare a caso in cui le variabili globali sono saggi ed è relativamente raro. Utili, nuovi programmi così piccoli che possono essere tenuti completamente nella memoria a breve termine del cervello sono sempre meno frequenti, ma esistono ancora.

In effetti, potrei affermare coraggiosamente che se il programma non è così piccolo, le variabili globali dovrebbero essere illegali.

  • Se la variabile non cambierà mai, allora è una costante, non una variabile.
  • Se la variabile richiede l’accesso universale, allora dovrebbero esistere due subroutine per ottenere e impostarlo, e dovrebbero essere sincronizzate.
  • Se il programma inizia in piccolo, e potrebbe essere più grande in seguito, codifica come se il programma fosse big oggi e abolisse le variabili globali. Non tutti i programmi cresceranno! (Anche se, naturalmente, ciò presuppone che il programmatore sia disposto a gettare via il codice, a volte.)

Le variabili globali in C sono utili per rendere il codice più leggibile se una variabile è richiesta da più metodi (piuttosto che passare la variabile in ogni metodo). Tuttavia, sono pericolosi perché tutte le posizioni hanno la possibilità di modificare quella variabile, rendendo potenzialmente difficile rintracciare i bug. Se è necessario utilizzare una variabile globale, assicurarsi sempre che venga modificato solo direttamente da un metodo e che tutti gli altri chiamanti utilizzino tale metodo. Ciò renderà molto più facile il debug di problemi relativi alle modifiche in quella variabile.

Quando non sei preoccupato del codice thread-safe : usali ovunque abbia senso, in altre parole ovunque abbia senso esprimere qualcosa come uno stato globale.

Quando il tuo codice potrebbe essere multi-thread : evitare a tutti i costi. Variabili globali astratte in code di lavoro o qualche altra struttura thread-safe, o se assolutamente necessario avvolgerle in blocchi, tenendo presente che questi sono probabilmente dei colli di bottiglia nel programma.

Sono venuto dal campo “mai”, fino a quando ho iniziato a lavorare nel settore della difesa. Esistono alcuni standard di settore che richiedono che il software utilizzi variabili globali anziché memoria dynamic (malloc nel caso C). Devo riconsiderare il mio approccio all’allocazione dynamic della memoria per alcuni dei progetti su cui lavoro. Se è ansible proteggere la memoria “globale” con i semafori, i thread, ecc. Appropriati, allora questo può essere un approccio accettabile per la gestione della memoria.

La complessità del codice non è l’unica ottimizzazione di interesse. Per molte applicazioni, l’ottimizzazione delle prestazioni ha una priorità molto maggiore. Ma ancora più importante, l’uso di variabili globali può ridurre drasticamente la complessità del codice in molte situazioni. Ci sono molte situazioni, forse specializzate, in cui le variabili globali non sono solo una soluzione accettabile, ma preferite. Il mio esempio specializzato preferito è il loro uso per fornire la comunicazione tra il thread principale di un’applicazione con una funzione di callback audio in esecuzione in un thread in tempo reale.

È fuorviante suggerire che le variabili globali siano una responsabilità nelle applicazioni multi-thread poiché QUALSIASI variabile, indipendentemente dall’ambito, è una potenziale responsabilità se è esposta a modifiche su più thread.

Usa le variabili globali con parsimonia. Le strutture di dati dovrebbero essere utilizzate ogniqualvolta ansible per organizzare e isolare l’uso dello spazio dei nomi globale.

I programmatori di avalli a portata variabile programmatori molto utile protezione – ma può avere un costo. Sono venuto a scrivere su variabili globali stasera perché sono un programmatore esperto di Objective-C che spesso si sente frustrato dalle barriere che ostacolano l’orientamento degli oggetti all’accesso ai dati. Direi che il fanatismo anti-globale proviene in gran parte da programmatori più giovani e dotati di una teoria sperimentata principalmente con API orientate agli oggetti in isolamento senza una profonda esperienza pratica di API a livello di sistema e la loro interazione nello sviluppo di applicazioni. Ma devo ammettere che mi sento frustrato quando i venditori usano il namespace in modo scomodo. Diverse distribuzioni di Linux avevano “PI” e “TWOPI” predefiniti a livello globale, ad esempio, che hanno rotto gran parte del mio codice personale.

È necessario considerare in quale contesto verrà utilizzata anche la variabile globale. In futuro vuoi duplicare questo codice.

Ad esempio se si sta utilizzando un socket nel sistema per accedere a una risorsa. In futuro vorrete accedere a più di una di queste risorse, se la risposta è sì, starei lontano dai globali, in primo luogo quindi non sarà richiesto un refactoring importante.

È uno strumento come tutti gli altri abituati, ma non penso che siano cattivi.

Ad esempio, ho un programma che funziona davvero come un database online. I dati sono archiviati in memoria ma altri programmi possono manipolarli. Ci sono routine interne che agiscono molto come stored procedure e trigger in un database.

Questo programma ha centinaia di variabili globali, ma se ci pensi cos’è un database ma un numero enorme di variabili globali.

Questo programma è in uso da una decina d’anni attraverso molte versioni e non è mai stato un problema e lo rifarei in un minuto.

Devo ammettere che in questo caso le vars globali sono oggetti che hanno metodi usati per cambiare lo stato dell’object. Quindi rintracciare chi ha cambiato l’object mentre il debug non è un problema dato che posso sempre impostare un punto di interruzione sulla routine che cambia lo stato dell’object. O ancora più semplice, accendo semplicemente la registrazione integrata che registra le modifiche.

Le variabili globali dovrebbero essere utilizzate quando più funzioni necessitano di accedere ai dati o scrivere su un object. Ad esempio, se è necessario passare dati o un riferimento a più funzioni come un singolo file di registro, un pool di connessioni o un riferimento hardware a cui è necessario accedere attraverso l’applicazione. Ciò impedisce dichiarazioni di funzione molto lunghe e grandi allocazioni di dati duplicati.

Normalmente non si dovrebbero usare le variabili globali a meno che non sia assolutamente necessario, poiché le variabili globali vengono solo ripulite quando viene esplicitamente richiesto di farlo o il programma finisce. Se si esegue un’applicazione multi-thread, più funzioni possono scrivere sulla variabile contemporaneamente. Se hai un bug, rintracciare quel bug può essere più difficile perché non sai quale funzione sta cambiando la variabile. Si incontra anche il problema dei conflitti di denominazione a meno che non si usi una convenzione di denominazione che attribuisca esplicitamente un nome univoco alle variabili globali.

Quando dichiari le costanti.

Posso pensare a diversi motivi:

scopi di debug / testing (attenzione – non hanno testato questo codice):

 #include  #define MAX_INPUT 46 int runs=0; int fib1(int n){ ++runs; return n>2?fib1(n-1)+fib1(n-2):1; }; int fib2(int n,int *cache,int *len){ ++runs; if(n<=2){ if(*len==2) return 1; *len=2; return cache[0]=cache[1]=1; }else if(*len>=n) return cache[n-1]; else{ if(*len!=n-1) fib2(n-1,cache,len); *len=n; return cache[n-1]=cache[n-2]+cache[n-3]; }; }; int main(){ int n; int cache[MAX_INPUT]; int len=0; scanf("%i",&n); if(!n||n>MAX_INPUT) return 0; printf("fib1(%i)==%i",n,fib1(n)); printf(", %i run(s)\n",runs); runs=0; printf("fib2(%i)==%i",n,fib2(n,&cache,&len)); printf(", %i run(s)\n",runs); main(); }; 

Ho usato le variabili scope per Fib2, ma quello è uno scenario in più in cui i globali potrebbero essere utili (pure funzioni matematiche che richiedono di archiviare i dati per evitare di impiegarli per sempre).

programmi utilizzati una sola volta (ad esempio per un concorso) o quando il tempo di sviluppo deve essere abbreviato

le variabili globali sono utili come costanti digitate, in cui una funzione da qualche parte richiede * int anziché int.

Generalmente evito i globali se intendo utilizzare il programma per più di un giorno.

  • Quando non usare: le variabili globali sono pericolose perché l’unico modo per sapere come è cambiata la variabile globale è tracciare l’intero codice sorgente all’interno del file .c all’interno del quale sono dichiarati (o, tutti i file .c se è esterno come bene). Se il tuo codice ha problemi, devi cercare i tuoi file di origine per vedere quali funzioni cambiano e quando. È un incubo fare il debug quando va male. Spesso diamo per scontato l’ingegno dietro il concetto di variabili locali che escono con garbo dall’ambito di applicazione – è facile rintracciare
  • Quando utilizzare: Le variabili globali devono essere utilizzate quando il loro utilizzo non è mascherato eccessivamente e il costo dell’utilizzo di variabili locali è eccessivamente complesso al punto in cui compromette la leggibilità. Con questo intendo il necessario di dover aggiungere un parametro aggiuntivo per far funzionare gli argomenti e restituire e passare i puntatori, tra le altre cose. Tre esempi classici: quando utilizzo il pop e il push stack, questo è condiviso tra le funzioni. Ovviamente potrei usare le variabili locali ma poi dovrei passare i puntatori come parametro aggiuntivo. Il secondo esempio classico è disponibile in “The C Programming Language” di K & R in cui definiscono le funzioni getch () e ungetch () che condividono un array di buffer di caratteri globali. Ancora una volta, non abbiamo bisogno di renderlo globale, ma la complessità aggiunta vale la pena quando è piuttosto difficile rovinare l’uso del buffer? Il terzo esempio è qualcosa che troverai nello spazio embedded tra gli appassionati di Arduino. Un sacco di funzioni all’interno della funzione del ciclo principale condividono tutte la funzione millis () che è l’istante in cui la funzione viene invocata. Poiché la velocità di clock non è infinita, millis () sarà diversa all’interno di un singolo loop. Per renderlo costante, scatta un’istantanea del tempo prima di ogni ciclo e salvalo in una variabile globale. L’istantanea temporale sarà la stessa di quando si accede alle numerose funzioni.
  • Alternative: non molto. Rispettare il più ansible l’ambito locale, soprattutto all’inizio del progetto, piuttosto che viceversa. Man mano che il progetto cresce e se senti che la complessità può essere ridotta usando le variabili globali, allora fallo, ma solo se soddisfa i requisiti del punto due. E ricorda, l’uso dell’ambito locale e il fatto di avere un codice più complicato è il male minore rispetto all’utilizzo irresponsabile delle variabili globali.

Sono nel campo “mai” qui; se hai bisogno di una variabile globale, almeno utilizza un modello singleton . In questo modo, raccogli i benefici dell’istanziazione pigra e non ingombrerai lo spazio dei nomi globale.

Le costanti globali sono utili: ottieni più sicurezza del tipo rispetto alle macro pre-processore ed è ancora più facile modificarne il valore se decidi di averne bisogno.

Le variabili globali hanno alcuni usi, ad esempio se l’operazione di molte parti di un programma dipende da uno stato particolare nella macchina a stati. Finché si limita il numero di posti che possono MODIFICARE la variabile che tiene traccia dei bug che lo coinvolgono non è male.

Le variabili globali diventano pericolose non appena si creano più thread. In tal caso, si dovrebbe limitare l’ambito al (al massimo) un file globale (dichiarandolo statico) e metodi getter / setter che lo proteggono da accessi multipli laddove ciò potrebbe essere pericoloso.