Newsgroup: it.comp.lang.c
Titolo: Solito dilemma: variabili globali oppure funzioni a go go?

Mi trovo spesso a dover affrontare il solito dilemma e, siccome non ho
una soluzione valida, ogni volta faccio diversamente.
Utilizzando una tecnica di programmazione modulare, cerco di suddivere
il programma in tanti moduli che offrono dei servizi ad altri moduli.
Senza pensare a librerie, per ogni modulo creo un file .c con il
codice e un file .h con l'interfaccia esterna (in pratica, i tipi e i
prototipi di funzione).
Il meccanismo funziona ed è molto elegante, ma mi costringe a creare
una funzione per qualsiasi cosa, per evitare di esportare variabili
visibili globalmente. Allo stesso modo potrei nascondere all'esterno
anche eventuali struct che rimarrebbero dipendenti
dall'implementazione.
Magari c'è dieci flag di configurazione del modulo, dalle funzioni più
disparate, e quindi sono costretto a creare la funzione leggi_flag1()/
scrivi_flag1(),  leggi_flag2()/scrivi_flag2(), ecc.
L'altra possibilità è quella di esportare le struct, i tipi e le
variabili rendendole globali. Non mi piace il metodo da un punto di
vista teorico, però è molto comodo.
Istruzioni del tipo
  mystruct.flag1 = 1;
  mystruct.flag2 = 0;
sono più efficienti (nessuna chiamata a funzione con tanto di
parametri da passare nello stack), ma necessitano di una visibilità
globale di mystruct e della sua definizione.
Un piccolo rimedio ai problemi di questo secondo metodo è quello di
definire, nel file .h del modulo, delle macro che assomiglino a delle
funzioni, ma che, alla fine, agiscono direttamente sulle variabili
globali.
Voi normalmente come procedete? Come fare a scegliere se creare una
interfaccia "solo funzioni" per qualsiasi cosa (e quindi rendere il
codice all'esterno indipendente dall'applicazione), oppure rendere
visibili tutte le variabili ed agire direttamente su di esse, rendendo
il codice più snello, ma meno elegante?


Le variabili globali sono il male (tm)
Non è questione di eleganza: più usi variabili globali più aumentano
le possibilità d'errore - e gli errori che derivano dalla scrittura
non desiderata di variabili globali sono veramente difficili da
identificare.
Personalmente tratto ogni insieme .c/.h (come dici te) come fosse
un'oggetto: tutto separato, definizioni solo interne e funzioni e
variabili dichiarate static se non strettamente necessario. Il fatto
di utilizzare il C come linguaggio di programmazione non vieta di
utilizzare uno schema architetturale (ma prima di tutto mentale) ad
oggetti.
Ciao!
P.S. A volte non è possibile ragionare così per motivi di performance
(troppi passaggi di parametri, lentezza nell'indirezione di troppi
puntatori, etc.). Essendo questi casi *veramente* rari, non li terrei
nemmeno in considerazione  in una discussione generale.

 il codice più snello, ma meno elegante?
dipende dai casi.
in genere e' meglio non usare le globali e fare funzioni rientranti.
a volte pero' l'utilizzo (accorto) di globali riduce la complessita' e
rende piu' leggibile il codice.
Ad esempio in ambito embedded l'uso di globali e' spesso necessario a
causa delle risorse limitate, per non sovvracaricare lo stack e per
non perdere preziosi cicli di processore.
Insomma dipende.
Ciao Jack

 nemmeno in considerazione  in una discussione generale.
In realtà in un contesto embedded, dove la potenza di calcolo, la
velocità
e la dimensione delle memorie sono limitati, questi casi non sono per
niente rari e l'utilizzo di una globale ti aiuta molto.
E' in queste situazioni che il dilemma si presenta ogni volta.

 Insomma dipende.
Infatti ogni volta mi viene questo dilemma e lo risolvo ogni volta in
modo diverso.
Tu come procedi per scegliere qual è l'approccio migliore?

 Tu come procedi per scegliere qual è l'approccio migliore?
cerco di evitare le globali se posso. Come detto a volte non se ne
puo' fare a meno, specialmente se hai a che fare con Interrupt Service
Routine (ISR).
normalmente le mie globali sono delle flag o roba che e' legata alle
ISR, inoltre normalmente sono anche tutte "volatile" proprio perche'
l'accesso non e' deterministico.
per il resto tratto i .c/.h come ha detto FtM.
Uso spesso anche l'inline per le funzioni get/set, in questo modo nel
codice hai una funzione (con tutti i vantaggi di leggibilita'), mentre
nel compilato non hai l'overhead della funzione a livello di tempo di
esecuzione, ma il footprint diventa (forse) piu' grande.
Ciao Jack

 ().
La mia filosofia di programmazione in C e' un po' diversa e non avrei
mai delle funzioni cosi'.
Pure nell'OO le filosofie sono cambiate: adesso le eccezzioni sono
errori, i getter/setter ormai non si usano piu'...

 esecuzione, ma il footprint diventa (forse) piu' grande.
In effetti le inline mi hanno sempre affascinato, ma le ho sempre
evitate
per problemi di portabilità.

 mai delle funzioni cosi'.
E qual è la tua filosofia?

 per problemi di portabilità.
il codice scritto per l'embedded difficilmente e' portabile, e' troppo
legato all'hardware che c'e' sotto.
Ahh, io parlo di embedded piccolo eh, micro a 8bit, RAM < 1 KB, flash
< 100 kB. Per roba grossa e/o che usa un OS le cose sono un po'
diverse.
Ciao Jack


La mia e' quella di Linus Torvalds:
Il C non e' un linguaggio ad alto livello, ma e' un assembler
glorificato.
Lo usi per scrivere assembler quasi portabile, e poi fai a guerra con
il compilatore per fargli fare quello che vuoi tu.
Tu qui stai parlando di getters/setters.
Non sta a me decidere se vanno bene o male nel tuo progetto.
In tutti quelli in cui ho dovuto partecipare io, si sono rivelati una
grande perdita di tempo.
Infatti,second me, se avessi bisogno di getters/setters, e'
l'architettura stessa il problema.
Non sto parlando di funzioni che fanno qualcosa: getters e setters
sono, specialmente adesso con linguaggi veramente ad alto livello tipo
Python, cose che vengono denominate 'decorazioni' in ambito OO.
Non hanno nulla a che fare con il C, IMHO.
Tutto li. Ma questa e' la mia filosofia, la quale inoltre dice che sei
tu il miglior giudice del tuo progetto: alla fin fine anche tu mi
sembra di aver capito noti che a volte sono utili, a volte no.
Ebbene, secondo me questa e' la base del C: fare le cose bene, ma non
farsi seghe mentali al riguardo.

 errori, i getter/setter ormai non si usano piu'...
Che cosa si usa al posto di get() / set() ?
Stavo appena imparando, devo cambiare di gia'? :)
Pleg


Questo è un classico pattern proxy (
http://it.wikipedia.org/wiki/
Proxy_pattern)
Tradotto in C:
static int is_fresh;
static int load_phisical() { ...; is_fresh=1; }
static int load_cached() { ... }
int invalidate() { is_fresh=0; }
int load_data()
{
    return (is_fresh)?load_cached():load_phisical();
}

 }
Sì, in effetti è qualcosa che faccio spesso. Il problema è che nel mio
esempio
non funziona perfettamente perchè i dati che vengono richiesti al
proxy sono sempre
diversi e non possono stare tutti in RAM. Se decidessi di mantenere in
RAM solo
l'ultimo valore richiesto, praticamente non riuscirei mai a
riutilizzarlo, ma sarei costretto
a leggere sempre dalla memoria.
Immagina però di visualizzare una lista di 100 dati e di doverla
rinfrescare ogni mezzo secondo.
La prima volta leggi i 100 dati e li scrivi. Dopo mezzo secondo magari
non è cambiato nulla,
perchè andare nuovamente a rileggere i dati dalla memoria che è lenta?
Io ho risolto con un flag. Ogni volta che accedo in scrittura alla
memoria attivo un flag.
Il modulo cliente quando vede questo flag alto, richiede tutti i dati
e resetta il flag.
Questo vuol dire che il flag deve essere globale ed anche modificabile
dall'esterno.
Oppure devo fare la classica get_flag()/set_flag().


Morale della favola, secondo te è meglio evitare di decorare il C e di
creare getters e setters, molto meglio programmare mettendo tutto
in variabili globali e accedendo direttamente ad esse. Tanto il C è
quasi
un assembler.
In parte hai ragione, ma spesso i programmi sono molto complessi e il
linguaggio C si usa ancora nella stragrande maggiranza dei casi (vedi
tutto
il software GNU o il mondo embedded). Non penso che questi
programmatori
dovrebbero prendere sotto gamba questioni come quella che mi sono
posto
io.

 io.
Se li pongono, non li prendono sottogamba, hanno semplicemente un
approccio piu' pragmatico.

 approccio piu' pragmatico.
Forse è quello che manca a me che sono un teorico :)

> Dipende dal linguaggio OO che stai imparando. 
Sto studiacchiando il C++ (non direi proprio imparando, e' per interesse
personale e un pochino per il lavoro, ma non scrivero' mai grandi quantita'
di codice industriale).
Pleg


Il C++ e' un linguaggio con una grande legacy, quindi se lo impari
oggi, ti scontrerai con diversi modi di interpretare il linguaggio che
si sono evoluti negli anni.
Io imparai il C nel '90 e il C++ nel '93. All'epoca l'unico libro che
trovai fu la prima edizione dello Stroustrup: non esistevano le
eccezzioni, non esisteva l'ereditarieta' multipla, non esisteva RTTI,
templates... non c'era nemmeno un vero standard.
Col tempo le cose si sono evolute.
Ma ad evolversi, in C++, ci hanno impiegato molti anni. E quindi molti
ingegneri informatici che ci sono passati anche per 5 anni della loro
carriera, si sono formati delle basi difficili da eradicare.
Un esempio e' l'uso di eccezzioni.
Per molto tempo, le eccezzioni erano viste, in C++, solo come
eccezzioni, ovvero casi eccezzionali, non errori, ed era considerato
male usarle come 'errori', addirittura alcuni dicevano che se usavi le
eccezzioni come normali errori, allora non avevi capito lo spirito del
C++ etc. etc....
Indovina oggi qual'e' la tendenza? ebbene si, le 'eccezzioni' non sono
piu' tali. Sono errori, fanno parte del flusso del programma e vanno
usate come tali. In Python, D, Java (D e' l'erede spirituale del C++).
Il motivo? Semplice: quando erano state introdotte, molti compilatori
non le supportavano. Microsoft era all'avanguardia rispetto ad altri
in questo settore, ma la loro implementazione faceva pena.
Siccome il C++ ha anche l'eredita' (secondo me errata) del C, ovvero
prestazioni a basso livello, implementare una eccezzione per un
semplice errore di apertura file sembrava un'eresia, dato il costo
dello stack unwinding al tempo.
Le cose ovviamente sono cambiate, e si e' ritornati alla conclusione
logica: se il linguaggio ti permette di usare eccezzioni, usale come
errori.
Ma ti scontrerai con gente che quando ha imparato il C++, si sentiva
all'avanguardia, ma ha smesso di imparare non tanto il linguaggio, ma
gli altri linguaggi e le pratiche in altri linguaggi.
Il C++ soffre molto della sindrome 'se lo fanno gli altri, lo faccio
anch'io'.
Il C per fortuna ha fatto pochi passi in tale direzione (tranne quello
di inventarsi un tipo void *, quando char * andava benissimo... ma
quello e' un piccolo pet-hate mio) e la maggior parte nella direzione
giusta (io uso quasi esclusivamente C99, a meno che non sia supportato
sulla piattaforma).

 Forse è quello che manca a me che sono un teorico :)
Questo non sta a me giudicarlo.
Ma a volte il pensare in astratto troppo presto puo' prevenire
soluzioni piu' eleganti dopo, quando si e' appreso come il concetto
viene usato, la *vera* business logic del programma.

[cut]
Grazie della spiega!
Pleg

 ritornasse un tipo slegato da vincoli.
Beh, questo però dà ragione a me che ho paura ad utilizzare variabili
globali, anche se evita di esportare la funzionalità associata alla
variabile
semplicemente tornandone il valore con una funzione get().
Però se questa variabile è molto semplice, tipo un flag (vedi
l'esempio
che ho riportato qui:
http://groups.google.com/group/it.comp.lang.c/msg/4f4461a165157281
)
non puoi fare altro che una get().
Chiamarla funzione che esporta una funzionalità è uguale a dire
esportare
il valore della variabile (1 o 0) ed è quindi praticamente uguale ad
avere
una variabile globale. Ma come al solito, è cosa giusta avere questa
variabile globale oppure implementare una get()?


 variabile globale oppure implementare una get()?
Ok, io allora chiamerei la tua get/set_flag: is_data_ready() e
data_ready(), o ancora meglio, metterei le funzioni che estraggono i
dati e riempiono di dati, nello stesso modulo, e dimenticati il flag
globale.
Cavolo, mi sono appena fatto una sega mentale... mannaggia a te! ;-)


Queste pagine valgono quanto valgono.
Ti ricordo comunque che la prima causa di un branching sbagliato a run-
time è la presenza di un if nel codice. Non usare gli if, sono il male!


Ragazzi, non mi fate fare la figura dello stupido. Non voglio prendere
per
oro colato quello che si dice in quella pagina. Era solo per citare
alcune
delle problematiche nell'utilizzo delle variabili globali. E' chiaro
che
quando servono si usano, ci mancherebbe. La mia discussione era solo
per definire meglio il limite oltre il quale è meglio un approccio più
robusto.

 globale.
Però questo mi costringe ad utilizzare delle strutture complesse per
fare il passaggio di dati, oppure a fare cose brutte.
Se devo visualizzare su un display una lista di stringhe da
recuperare
dalla memoria non volatile devo fare una funzione nel modulo di
accesso
alla memoria che scrive su display? Mi sembra un incrocio sporco di
funzionalità...



Comunque mi sembrano buoni spunti.
Per quanto riguarda non avere 'if'... ne avrei da dire, ma rivelerebbe
segreti aziendali :-( *sniff*


il segreto aziendale è che in realtà scrivete in smalltalk, vero? :D

 il segreto aziendale è che in realtà scrivete in smalltalk, vero? :D
peggio: C++ templates!