Newsgroup: it.comp.lang.c
Titolo: Dichiarare variabili e definirle extern per usarle altrove

Mi capita spesso di dichiarare delle variabili in un file .c e di
doverle utilizzare, come variabili esterne, in altri moduli .c. Questo
naturalmente mi obbliga a dichiararle extern da qualche parte,
normalmente in un file .h.
Per esempio, per raccogliere tutte le stringhe utilizzate nel
programma potrei fare qualcosa del genere:
--- strings.c ---
const char str1[] = "Prima stringa";
const char str2[] = "Seconda stringa";
...
--- fine strings.c ---
--- strings.h ---
extern const char str1[];
extern const char str2[];
...
--- fine strings.h ---
Questo mi permette di fare riferimento ad una qualsiasi stringa da
qualsiasi parte del programma, con l'unico vincolo di includere il
file strings.h.
E' ovvio che questo metodo ha il suo svantaggio. Quando devo
aggiungere o togliere una stringa, sono costretto a modificare due
file: il file strings.h e il file strings.c. Per risolvere questo
problema potrei fare così:
--- strings.h ---
#ifdef DEFINE_STRINGS
  #define STRING(strVar, str) const char strVar[] = str;
#else
  #define STRING(strVar, str) extern const char strVar[];
#endif
STRING(str1, "Prima stringa");
STRING(str2, "Seconda stringa");
--- fine strings.h ---
--- strings.c ---
#define DEFINE_STRINGS
#include "strings.h"
#undef DEFINE_STRINGS
...
--- fine strings.c ---
Così facendo mi basta, come al solito, includere il file strings.h in
qualsiasi modulo .c dove mi serve un riferimento ad una stringa.
Inoltre ho un unico punto in cui fare le modifiche e cioè proprio nel
file strings.h.
In ultima analisi, il file strings.c diventa quasi inutile. Basterebbe
aggiungere le righe di codice di questo file in un altro modulo .c,
come per esempio il main.c.
Vorrei capire chi di voi utilizza quest'ultima tecnica e se voi la
ritenete accettabile per una buona programmazione, oppure vi affidate
al solito metodo dei due file (.c e .h) duplicando le dichiarazioni.


Personalmente non approvo. Secondo me le possibilità sono due:
1- Metti lo extern solo nella funzione dove serve. Lasci il .h pulito
e non riempi lo spazio dei nomi.
/* file1.c */
const char str[] = "yadayada";
/* file2.c */
void pippo(void)
{
    extern const char str[];
    ...
}
2- Definisci la variabile da esportare come statica, e scrivi una
funzione per restituirla.
/* file1.c */
static const char str[] = "yadayada";
const char *get_str(void)
{
    return str;
}
/* file1.h */
const char *get_str(void);
/* file2.c */
void pippo(void)
{
    ... get_str();
}
Ovviamente dipende dai casi, ma personalmente preferisco il secondo
approccio, in quanto ti consente di avere già in piedi un hook facile
da modificare. Oltretutto il metodo ricorda molto variabili private e
getter/setter dei linguaggi ad oggetti, per cui viene molto naturale.
Nemmeno per le performance occorre preoccuparsi perché ogni
compilatore decente riesce ad ottimizzare questo tipo di codice
eliminando la funzione.
Ciao!

 eliminando la funzione.
In effetti il parallelismo con i linguaggi ad oggetti è un grande
vantaggio.
Forse però è un approccio valido se hai tanti moduli, ognuno dei quali
fornisce un set di stringhe all'esterno.
Il mio è un approccio diverso. Non ho numerosi moduli, il programma
non è
enorme (ma comunque complicato). Avevo deciso di separare la parte a
video
(quindi le stringhe) in un unico file (strings.c) indipendentemente
dal modulo
di pertinenza.

 di pertinenza.
Sì, ovviamente dipende moltissimo dal modo di scrivere. Certamente
scrivere in C con un approccio OOP è tutto fuorché comodo ed
immediato, ma alla lunga posso dire per esperienza che lo sforzo
ripaga (la modularizzazione del codice insieme ad un sistema di
tracking di valori erronei di ritorno per le funzioni e della
allocazione della memoria possono far risparmiare *giorni* interi di
lavoro durante la ricerca e fix dei bachi, ad un prezzo di
performances praticamente nullo).
Ciao!

 compatta. Anche per il compilatore/linker il lavoro è differente.
Ho parlato di libreria, ma in realtà il discorso è simile per un
modulo
di un tuo programma che non necessariamente forma una libreria
precompilata da linkare.
Immagina di avere un modulo mymath.c dove scrivo un po' di
funzioni matematiche che mi servono nel mio programma. Faccio
un mymath.h con i prototipi e sono a posto. In un modulo dove
devo fare riferimento ad una funzione di mymath.c, basta che
includo mymath.h.
La stessa cosa dicasi per le variabili globali. Se ci sono e vengono
messe a disposizione da un modulo, dovrebbe essere sufficiente
includere il .h di quel modulo e non essere costretti a dichiarare
le variabili che mi servono con un extern nel mio modulo.

In effetti mi sto rendendo conto che è meglio "sforzarsi", piuttosto
che
automatizzare troppo...

Sono perfettamente d'accordo, soprattutto sulla modularizzazione.
Hai qualche documento/libro/articolo/sito dove si tratta di queste
problematiche legate alla programmazione in C?
Mi rendo conto che spesso perdo MOLTISSIMO tempo non perchè
non so fare una cosa, tutt'altro. In pratica, una stessa cosa potrei
implementarla in mille modi diversi ed inizio a farmi un sacco di
paranoie mentali su come andrebbe scritta nel modo migliore (vedi
proprio il problema delle stringhe costanti della mia applicazione
e la loro "globalizzazione").


Ovviamente non c'è un metodo unico, e che io sappia la programmazione
C è molto più "libera" rispetto ad altri linguaggi, nel senso che non
c'è quasi mai un rapporto 1:1 con il disegno software, cosa che invece
si tende a fare con i linguaggi ad oggetti (basta pensare che a volte
in C++ o in Java il codice lo si genera automaticamente dallo UML...)
Secondo me la via da seguire nello scegliere una base per un nuovo
progetto è leggere molto codice altrui, anche di altri tipi di
progetti, e prendere lo stile che meglio si addice al problema da
affrontare.
Personalmente sono un fan sia dello stile della GLib che di quello
delle Coreutils, che direi proprio essere ai due margini opposti delle
due buone filosofie di programmazione in C.
Ciao!