In C / C ++, le variabili globali sono cattive come pensa il mio professore?
In C / C ++, le variabili globali sono cattive come pensa il mio professore?
Risposte:
Il problema con le variabili globali è che dal momento che ogni funzione ha accesso a queste, diventa sempre più difficile capire quali funzioni effettivamente leggono e scrivono queste variabili.
Per capire come funziona l'applicazione, devi praticamente prendere in considerazione ogni funzione che modifica lo stato globale. Questo può essere fatto, ma man mano che l'applicazione cresce, diventerà più difficile al punto da essere praticamente impossibile (o almeno una completa perdita di tempo).
Se non si fa affidamento su variabili globali, è possibile passare lo stato tra le diverse funzioni secondo necessità. In questo modo hai maggiori possibilità di capire cosa fa ogni funzione, poiché non è necessario tenere conto dello stato globale.
L'importante è ricordare l'obiettivo generale: la chiarezza
La regola "nessuna variabile globale" è lì perché la maggior parte delle volte, le variabili globali rendono meno chiaro il significato del codice.
Tuttavia, come molte regole, le persone ricordano la regola e non ciò che la regola doveva fare.
Ho visto programmi che sembrano raddoppiare la dimensione del codice passando un numero enorme di parametri semplicemente per evitare il male delle variabili globali. Alla fine, l'uso dei globali avrebbe reso il programma più chiaro a coloro che lo leggevano. Aderendo inconsapevolmente alla parola della regola, il programmatore originale aveva fallito l'intento della regola.
Quindi, sì, i globi sono spesso cattivi. Ma se senti che alla fine, l'intento del programmatore è reso più chiaro dall'uso delle variabili globali, allora vai avanti. Tuttavia, ricorda il calo di chiarezza che ne consegue automaticamente quando costringi qualcuno ad accedere a un secondo pezzo di codice (i globi) per capire come funziona il primo pezzo.
Il mio professore diceva qualcosa del genere: usare le variabili globali va bene se le usi correttamente. Non credo di essere mai riuscito a usarli correttamente, quindi li ho usati raramente.
static
molto le variabili globali, il linguaggio è C. Essendo limitato a unità di traduzione relativamente piccole, iniziano a somigliare alle variabili di classe degli oggetti C ++.
program lifetime, file scope variables
. E diventano abbastanza globali quando si passa un puntatore alla variabile verso il mondo esterno (cosa impossibile con le variabili automatiche) ..
static
le variabili globali hanno portata limitata alla stessa unità di traduzione. Ma hanno una durata fino alla fine del programma come qualsiasi variabile globale.
Le variabili globali dovrebbero essere utilizzate solo quando non si hanno alternative. E sì, questo include Singletons. Nel 90% dei casi, vengono introdotte variabili globali per risparmiare sui costi di passaggio di un parametro. Quindi si verifica il multithreading / test unit / codifica di manutenzione e si verifica un problema.
Quindi sì, nel 90% delle situazioni le variabili globali sono cattive. È improbabile che le eccezioni siano visibili da te negli anni del college. Un'eccezione che mi viene in mente è quella di occuparmi di oggetti intrinsecamente globali come le tabelle di interrupt. Cose come la connessione DB sembrano essere globali, ma non lo sono.
Il problema che le variabili globali creano per il programmatore è che espande la superficie di accoppiamento tra i componenti tra i vari componenti che utilizzano le variabili globali. Ciò significa che all'aumentare del numero di componenti che utilizzano una variabile globale, può aumentare anche la complessità delle interazioni. Questo maggiore accoppiamento di solito rende più facile iniettare i difetti nel sistema quando si apportano modifiche e rende anche i difetti più difficili da diagnosticare e correggere. Questo aumento dell'accoppiamento può anche ridurre il numero di opzioni disponibili quando si apportano modifiche e può aumentare lo sforzo richiesto per le modifiche, poiché spesso è necessario tracciare attraverso i vari moduli che utilizzano anche la variabile globale al fine di determinare le conseguenze delle modifiche.
Lo scopo dell'incapsulamento , che è sostanzialmente l'opposto dell'uso delle variabili globali, è di ridurre l'accoppiamento al fine di rendere la comprensione e la modifica della fonte più semplice e sicura e più facilmente testata. È molto più semplice utilizzare i test unitari quando non vengono utilizzate variabili globali.
Ad esempio, se si dispone di una semplice variabile intera globale che viene utilizzata come indicatore enumerato che vari componenti utilizzano come macchina a stati e quindi si effettua una modifica aggiungendo un nuovo stato per un nuovo componente, è quindi necessario tracciare tutti gli altri componenti per garantire che la modifica non li riguardi. Un esempio di un possibile problema potrebbe essere se switch
un'istruzione per testare il valore della variabile globale di enumerazione con case
istruzioni per ciascuno dei valori correnti viene utilizzata in vari punti e succede che alcune delle switch
istruzioni non hanno un default
caso da gestire un valore inaspettato per il globale all'improvviso hai un comportamento indefinito per quanto riguarda l'applicazione.
D'altra parte, l'uso di un'area di dati condivisa potrebbe essere utilizzata per contenere un insieme di parametri globali a cui viene fatto riferimento in tutta l'applicazione. Questo approccio viene spesso utilizzato con applicazioni integrate con ingombri di memoria ridotti.
Quando si utilizzano variabili globali in questo tipo di applicazioni, in genere la responsabilità di scrivere nell'area dati viene assegnata a un singolo componente e tutti gli altri componenti vedono l'area come const
e letta da essa, senza mai scriverla. Adottare questo approccio limita i problemi che possono svilupparsi.
Alcuni problemi dovuti alle variabili globali che devono essere risolti
Quando l'origine di una variabile globale come una struttura viene modificata, tutto ciò che la utilizza deve essere ricompilato in modo che tutto ciò che utilizza la variabile conosca la sua dimensione reale e il modello di memoria.
Se più di un componente può modificare la variabile globale, è possibile riscontrare problemi con dati incoerenti nella variabile globale. Con un'applicazione multi-thread, sarà probabilmente necessario aggiungere un tipo di blocco o area critica per fornire un modo in modo che solo un thread alla volta possa modificare la variabile globale e quando un thread modifica la variabile, tutte le modifiche sono complete e impegnati prima che altri thread possano interrogare la variabile o modificarla.
Il debug di un'applicazione multi-thread che utilizza una variabile globale può essere più difficile. È possibile imbattersi in condizioni di gara che possono creare difetti difficili da replicare. Con diversi componenti che comunicano attraverso una variabile globale, specialmente in un'applicazione multi-thread, essere in grado di sapere quale componente sta cambiando la variabile quando e come sta cambiando la variabile può essere molto difficile da capire.
Lo scontro tra nomi può essere un problema con l'utilizzo di variabili globali. Una variabile locale che ha lo stesso nome di una variabile globale può nascondere la variabile globale. Si verifica anche il problema della convenzione di denominazione quando si utilizza il linguaggio di programmazione C. Una soluzione consiste nel dividere il sistema in sottosistemi con le variabili globali per un particolare sottosistema che iniziano tutte con le stesse prime tre lettere (vedere questo sulla risoluzione delle collisioni dello spazio dei nomi nell'obiettivo C ). C ++ fornisce spazi dei nomi e con C puoi aggirare il problema creando una struttura visibile a livello globale i cui membri sono vari elementi di dati e puntatori a dati e funzioni che sono forniti in un file come statici, quindi con visibilità del file solo in modo che possano essere referenziati solo attraverso la struttura visibile a livello globale.
In alcuni casi, l'intento originale dell'applicazione viene modificato in modo da modificare le variabili globali che hanno fornito lo stato di un singolo thread per consentire l'esecuzione di più thread duplicati. Un esempio potrebbe essere una semplice applicazione progettata per un singolo utente che utilizza variabili globali per stato e quindi una richiesta scende dalla direzione per aggiungere un'interfaccia REST per consentire alle applicazioni remote di agire come utenti virtuali. Quindi ora ti imbatti nel dover duplicare le variabili globali e le loro informazioni sullo stato in modo che il singolo utente e ciascuno degli utenti virtuali delle applicazioni remote abbiano il proprio set unico di variabili globali.
Utilizzando C ++ namespace
e la struct
tecnica per C
Per il linguaggio di programmazione C ++ la namespace
direttiva è di grande aiuto nel ridurre le possibilità di uno scontro di nomi. namespace
insieme a class
e le varie parole chiave di accesso ( private
, protected
e public
) forniscono la maggior parte degli strumenti necessari per incapsulare le variabili. Tuttavia, il linguaggio di programmazione C non fornisce questa direttiva. Questa pubblicazione di stackoverflow, Namespace in C , fornisce alcune tecniche per C.
Una tecnica utile è quella di avere una singola area dati residente in memoria definita come una struct
che abbia visibilità globale e all'interno di essa vi struct
siano puntatori alle varie variabili e funzioni globali che vengono esposte. Alle definizioni effettive delle variabili globali viene assegnato l'ambito del file utilizzando la static
parola chiave. Se poi usi la const
parola chiave per indicare quali sono di sola lettura, il compilatore può aiutarti a imporre l'accesso in sola lettura.
L'uso della struct
tecnica può anche incapsulare il globale in modo che diventi una specie di pacchetto o componente che sembra essere un globale. Avendo un componente di questo tipo diventa più facile gestire i cambiamenti che influenzano il globale e la funzionalità usando il globale.
Tuttavia, mentre namespace
o la struct
tecnica può aiutare a gestire gli scontri con i nomi, esistono ancora i problemi sottostanti dell'accoppiamento tra componenti che l'uso di globuli introduce soprattutto in una moderna applicazione multi-thread.
Sì, ma non devi sostenere il costo delle variabili globali finché non smetti di lavorare nel codice che utilizza le variabili globali e inizi a scrivere qualcos'altro che utilizza il codice che utilizza le variabili globali. Ma il costo è ancora lì.
In altre parole, è un costo indiretto a lungo termine e come tale la maggior parte delle persone pensa che non sia male.
Se è possibile, il tuo codice verrà sottoposto a un'attenta revisione durante un processo alla Corte Suprema , quindi assicurati di evitare le variabili globali.
Vedi questo articolo: Il codice dell'etilometro Buggy riflette l'importanza della revisione della fonte
Ci sono stati alcuni problemi con lo stile del codice identificati da entrambi gli studi. Una delle questioni stilistiche che riguardavano i revisori era l'uso estensivo di variabili globali non protette . Questa è considerata una forma scadente perché aumenta il rischio che lo stato del programma diventi incoerente o che i valori vengano inavvertitamente modificati o sovrascritti. I ricercatori hanno anche espresso preoccupazione per il fatto che la precisione decimale non viene mantenuta in modo coerente in tutto il codice.
Scommetto che quegli sviluppatori sperano di non aver usato variabili globali!
Le variabili globali sono cattive quanto le crei, non meno.
Se stai creando un programma completamente incapsulato, puoi usare i globi. È un "peccato" usare i globuli, ma i peccati di programmazione sono pessimi filosofici.
Se dai un'occhiata a L.in.oleum , vedrai una lingua le cui variabili sono esclusivamente globali. Non è scalabile perché tutte le librerie non hanno altra scelta che usare i globi.
Detto questo, se hai delle scelte e puoi ignorare la filosofia del programmatore, i globi non sono poi così male.
Né sono Goto, se li usi nel modo giusto.
Il grosso "cattivo" problema è che, se li usi in modo sbagliato, le persone urlano, il lander di Marte si schianta e il mondo esplode ... o qualcosa del genere.
Risponderei a questa domanda con un'altra domanda: usi i singelton / I singelton sono cattivi?
Perché (quasi tutti) l'utilizzo di singelton è una variabile globale glorificata.
Il problema è meno che sono cattivi e più che sono pericolosi . Hanno il loro insieme di pro e contro e ci sono situazioni in cui sono il modo più efficiente o unico per raggiungere un determinato compito. Tuttavia, sono molto facili da usare in modo improprio, anche se si prendono provvedimenti per usarli sempre correttamente.
Alcuni professionisti:
Alcuni svantaggi:
Nota, se vuoi, che i primi due pro e i primi due contro che ho elencato sono esattamente la stessa cosa, solo con una diversa formulazione. Questo perché le caratteristiche di una variabile globale possono davvero essere utili, ma le stesse caratteristiche che le rendono utili sono la fonte di tutti i loro problemi.
Alcune potenziali soluzioni ad alcuni dei problemi:
Globals
o GlobalVars
) oppure usa una convenzione di denominazione standardizzata per le variabili globali (come global_[name]
o g_module_varNameStyle
(come indicato da underscore_d nei commenti )). Ciò documenterà entrambi il loro utilizzo (è possibile trovare il codice che utilizza le variabili globali cercando lo spazio dei nomi / nome struttura) e minimizzare l'impatto sullo spazio dei nomi globale.extern
nell'intestazione associata, in modo che il loro uso possa essere limitato alle unità di compilazione che devono accedervi. Se il tuo codice si basa su molte variabili globali, ma ogni unità di compilazione necessita solo dell'accesso a una manciata di esse, potresti prendere in considerazione l'ordinamento in più file di origine, quindi è più semplice limitare l'accesso di ciascun file alle variabili globali.Che siano buoni o cattivi dipende da come li usi. La maggior parte tende a usarli male, quindi la diffidenza generale nei loro confronti. Se usati correttamente, possono essere un grande vantaggio; se usato male, tuttavia, possono e potranno tornare a mordere voi quando e come meno te lo aspetti.
Un buon modo per vederlo è che essi stessi non sono cattivi, ma consentono un cattivo design e possono moltiplicare gli effetti del cattivo design in modo esponenziale.
Anche se non hai intenzione di usarli, è meglio sapere come usarli in sicurezza e scegliere di non farlo, piuttosto che non usarli perché non sai come usarli in sicurezza. Se ti trovi mai in una situazione in cui devi mantenere un codice preesistente che si basa su variabili globali, potresti avere difficoltà se non sai come usarle correttamente.
g_module_varNameStyle
perfettamente leggibile. Per essere chiari, non sto usando i globali se posso evitarlo facilmente - parola chiave facilmente , perché da quando ho smesso di credere che debbano essere evitati - o piuttosto offuscati - a tutti i costi, mi sto divertendo molto di più, e il mio il codice è (shock!) molto più ordinato
Come qualcuno ha detto (sto parafrasando) in un altro thread "Regole come questa non dovrebbero essere infrante, fino a quando non capirai appieno le conseguenze di farlo."
Ci sono momenti in cui sono necessarie variabili globali, o almeno molto utili (Lavorare con richiamate definite dal sistema, ad esempio). D'altra parte, sono anche molto pericolosi per tutti i motivi che ti è stato detto.
Ci sono molti aspetti della programmazione che probabilmente dovrebbero essere lasciati agli esperti. A volte hai BISOGNO di un coltello molto affilato. Ma non puoi usarne uno finché non sei pronto ...
Le variabili globali sono generalmente sbagliate, specialmente se altre persone lavorano sullo stesso codice e non vogliono spendere 20 minuti alla ricerca di tutti i luoghi a cui fa riferimento la variabile. E l'aggiunta di thread che modificano le variabili porta un nuovo livello di mal di testa.
Le costanti globali in uno spazio dei nomi anonimo utilizzato in una singola unità di traduzione sono eccellenti e onnipresenti nelle app e nelle librerie professionali. Ma se i dati sono mutabili e / o devono essere condivisi tra più TU, è possibile incapsularli, se non per motivi di progettazione, quindi per chiunque esegua il debug o lavori con il proprio codice.
L'uso di variabili globali è un po 'come spazzare lo sporco sotto un tappeto. È una soluzione rapida e molto più facile a breve termine rispetto a una padella per la polvere o un aspirapolvere per pulirla. Tuttavia, se finisci per spostare il tappeto più tardi, avrai un grande pasticcio a sorpresa sotto.
Penso che il tuo professore stia cercando di fermare una cattiva abitudine prima ancora che inizi.
Le variabili globali hanno il loro posto e come molte persone hanno detto sapere dove e quando usarle può essere complicato. Quindi penso piuttosto che entrare nel nocciolo del perché, come, quando e dove delle variabili globali il tuo professore ha deciso di vietare. Chissà, potrebbe annullarli in futuro.
Assolutamente no. Usandoli male però ... questo è male.
Rimuoverli senza ragione per il gusto di farlo è semplicemente ... insensato. A meno che tu non conosca i vantaggi e gli svantaggi, è meglio essere chiari e fare come ti è stato insegnato / appreso, ma non c'è nulla di implicitamente sbagliato nelle variabili globali. Quando capisci i pro e i contro, meglio prendere la tua decisione.
Le variabili globali vanno bene nei piccoli programmi, ma orribili se usate allo stesso modo in quelli grandi.
Ciò significa che puoi facilmente prendere l'abitudine di usarli mentre impari. Questo è ciò che il tuo professore sta cercando di proteggerti.
Quando avrai più esperienza, sarà più facile imparare quando stanno bene.
No, non sono affatto male. È necessario esaminare il codice (macchina) prodotto dal compilatore per prendere questa decisione, a volte è molto peggio usare un locale piuttosto che un globale. Nota anche che mettere "statico" su una variabile locale sta praticamente diventando un globale (e crea altri brutti problemi che un vero globale risolverebbe). i "globi locali" sono particolarmente cattivi.
I globali ti danno anche un controllo pulito sull'utilizzo della memoria, qualcosa di molto più difficile da fare con la gente del posto. In questi giorni è importante solo in ambienti embedded in cui la memoria è piuttosto limitata. Qualcosa da sapere prima di supporre che embedded sia uguale agli altri ambienti e supporre che le regole di programmazione siano le stesse su tutta la linea.
È bene che tu metta in dubbio le regole che ti vengono insegnate, la maggior parte di esse non è per i motivi per cui ti viene detto. La lezione più importante però non è che questa è una regola da portare con te per sempre, ma questa è una regola richiesta per onorare per passare questa classe e andare avanti. Nella vita scoprirai che per la società XYZ avrai altre regole di programmazione che alla fine dovrai rispettare per ottenere uno stipendio. In entrambe le situazioni puoi discutere la regola, ma penso che avrai una fortuna molto migliore in un lavoro che a scuola. Sei solo un altro di molti studenti, il tuo posto verrà presto sostituito, i professori non lo faranno, in un posto di lavoro sei uno di una piccola squadra di giocatori che deve vedere questo prodotto fino alla fine e in quell'ambiente le regole sviluppate sono per il beneficio dei membri del team nonché del prodotto e dell'azienda, quindi se a tutti piacciono o se per quel particolare prodotto ci sono buone ragioni ingegneristiche per violare qualcosa che hai imparato al college o qualche libro sulla programmazione generica, allora vendi la tua idea al team e scrivila come un metodo valido se non preferito . Tutto è un gioco leale nel mondo reale.
Se segui tutte le regole di programmazione che ti sono state insegnate a scuola o nei libri, la tua carriera di programmatore sarà estremamente limitata. Probabilmente puoi sopravvivere e avere una carriera fruttuosa, ma l'ampiezza e la larghezza degli ambienti a tua disposizione saranno estremamente limitate. Se sai come e perché esiste la regola e puoi difenderla, va bene, se la tua sola ragione è "perché il mio insegnante ha detto così", beh, non è così buono.
Nota che argomenti come questo sono spesso discussi sul posto di lavoro e continueranno ad esserlo, man mano che i compilatori e i processori (e le lingue) si evolvono, così fanno questo tipo di regole e senza difendere la tua posizione e possibilmente ti viene insegnata una lezione da qualcuno con un'altra opinione che non vorrai andare avanti.
Nel frattempo, fai semplicemente quello che dice il più forte o porta il bastone più grande (fino a quando non sei tu quello che grida più forte e porta il bastone più grande).
Vorrei discutere contro il punto sollevato in questa discussione che rende il multi-threading più difficile o impossibile di per sé. Le variabili globali sono stati condivisi, ma anche le alternative ai globi (ad esempio il passaggio di puntatori) potrebbero condividere lo stato. Il problema con il multi-threading è come utilizzare correttamente lo stato condiviso, non se tale stato è condiviso attraverso una variabile globale o qualcos'altro.
Il più delle volte quando fai multi-threading devi condividere qualcosa. In un modello produttore-consumatore, ad esempio, è possibile condividere alcune code thread-safe che contengono le unità di lavoro. E sei autorizzato a condividerlo perché quella struttura di dati è thread-safe. Il fatto che quella coda sia globale o meno è del tutto irrilevante quando si tratta di sicurezza dei thread.
La speranza implicita espressa in questo thread che trasformare un programma da single-thread a multi-thread sarà più facile quando non si usano i globi è ingenuo. Sì, i globi rendono più facile spararti al piede, ma ci sono molti modi per spararti.
Non sto sostenendo i globali, poiché gli altri punti sono ancora validi, il mio punto è semplicemente che il numero di thread in un programma non ha nulla a che fare con l'ambito variabile.
Sì, perché se lasci che vengano utilizzati da programmatori incompetenti (leggi il 90%, in particolare gli scienziati), finisci con oltre 600 variabili globali distribuite su oltre 20 file e un progetto di 12.000 linee in cui l'80% delle funzioni prende il vuoto, restituisce il vuoto e opera interamente sullo stato globale.
Diventa rapidamente impossibile capire cosa sta succedendo in qualsiasi momento a meno che tu non conosca l'intero progetto.
L'uso delle variabili globali dipende effettivamente dai requisiti. Il suo vantaggio è che, riduce il sovraccarico di passare ripetutamente i valori.
Ma il tuo professore ha ragione perché solleva problemi di sicurezza, quindi l'uso delle variabili globali dovrebbe essere evitato il più possibile. Le variabili globali creano anche problemi che a volte sono difficili da eseguire il debug .
Per esempio:-
Situazioni in cui i valori delle variabili vengono modificati in fase di esecuzione . In quel momento è difficile identificare quale parte del codice lo sta modificando e su quali condizioni.
Global è buono quando si tratta di configurazione . Quando vogliamo che la nostra configurazione / modifiche di avere un impatto globale sul tutto il progetto .
Quindi possiamo cambiare una configurazione e le modifiche sono dirette all'intero progetto . Ma devo avvertire che dovresti essere molto intelligente per usare i globi.
Prima o poi dovrai cambiare il modo in cui è impostata quella variabile o cosa succede quando si accede, oppure devi solo cercare dove è stata modificata.
È praticamente sempre meglio non avere variabili globali. Basta scrivere la diga get e impostare i metodi, ed essere ghiandola quando ne hai bisogno un giorno, settimana o mese dopo.
Di solito uso i globuli per valori che raramente vengono cambiati come singoli o puntatori a funzioni in una libreria caricata dinamicamente. L'uso di globuli mutabili in applicazioni multithread tende a causare bug difficili da rintracciare, quindi cerco di evitarlo come regola generale.
L'uso di un argomento globale invece di passare un argomento è spesso più veloce ma se stai scrivendo un'applicazione multithread, cosa che fai spesso al giorno d'oggi, in genere non funziona molto bene (puoi usare thread-statics ma il guadagno in termini di prestazioni è discutibile) .
Nelle applicazioni Web all'interno di un enterprize può essere utilizzato per conservare dati specifici di sessione / finestra / thread / utente sul server per motivi di ottimizzazione e per evitare la perdita di lavoro in cui la connessione è instabile. Come accennato, le condizioni di gara devono essere gestite. Usiamo una singola istanza di una classe per queste informazioni ed è gestita con cura.
Alla fine della giornata, il tuo programma o app può ancora funzionare ma è una questione di essere ordinato e avere una completa comprensione di ciò che sta succedendo. Se condividi un valore variabile tra tutte le funzioni, potrebbe essere difficile tracciare quale funzione sta modificando il valore (se la funzione lo fa) e rende più difficile il debug un milione di volte
la sicurezza è meno significa che chiunque può manipolare le variabili se sono dichiarate globali, per questo spiegherai questo esempio se hai un saldo come variabile globale nel tuo programma bancario, la funzione utente può manipolarla e anche un funzionario bancario può manipolare questo quindi c'è un problema. solo all'utente dovrebbe essere data la sola lettura e la funzione di prelievo, ma l'impiegato della banca può aggiungere l'importo quando l'utente dà personalmente i contanti al banco.questo è il modo in cui funziona
In un'applicazione multi-thread, utilizzare le variabili locali al posto delle variabili globali per evitare una condizione di competizione.
Una condizione di competizione si verifica quando più thread accedono a una risorsa condivisa, con almeno un thread con accesso in scrittura ai dati. Quindi, il risultato del programma non è prevedibile e dipende dall'ordine degli accessi ai dati da parte di thread diversi.
Maggiori informazioni qui, https://software.intel.com/en-us/articles/use-intel-parallel-inspector-to-find-race-conditions-in-openmp-based-multithreaded-code