View Full Version : [C] Variabili globali e header files
Sto facendo un programma in cui ho la necessità di utilizzare più *.c file e 1 *.h file .
Il programma necessità di una variabile globale.
Mi sono imbattuto però nel problema che dichiarandola e definendola nel file *.h ( incluso in TUTTI i file *.c ) il linker mi dice che la variabile è dublicata.
Ho cercato un po' su internet e ho visto che non sono stato l'unico ad avere questo problema.
Ho trovato una soluzione ma non mi sono ben chiari alcuni concetti:
Nel file *.header la variabile è dichiarata come segue: extern int max_game_time; Perchè devo metterla extern? Cosa ottengo facendo così?
Nei due file *.c la variabile è dichiarata così: int max_game_time;
Mi sarei aspettato invece di poter dichiarare la variabile globale nel file header e porla subito uguale a 0, e lasciarla solo dichiarata negli altri due file *.c . Invece non si può... Come mai?
Detto questo, è corretto questo modo di dichiarare variabili globali in file header inclusi in più *.c file?
C'è un modo migliore?
Inoltre, cosa significano:
#ifndef _NOMEHEADER_H_
#define _NOMEHEADER_H_
...
#endif
A cosa servono?
wingman87
09-12-2010, 21:56
Mi sembra di ricordare che per avere una variabile globale la devi dichiarare normalmente in uno dei file .c e in tutti gli altri dichiararla come extern.
#ifndef _NOMEHEADER_H_
#define _NOMEHEADER_H_
...
#endif
Queste sono direttive per il preprocessore, ifndef e define in particolare vengono usate in questo modo per assicurarsi che la parte all'interno dell'if non venga interpretata dal preprocessore più di una volta.
allora diciamo che questa:
int var;
è la definizione della variabile, che alloca lo spazio di cui necessita e ce ne può essere una sola
questa invece:
extern int var;
è una dichiarazione della variabile, che crea "un buco" nell'oggetto compilato che è compito del linker riempire
la definizione deve essere una sola, le dichiarazioni possono essere tante, per cui la soluzione che si utilizza di solito è inserire le dichiarazioni nei file header, che possono essere incluse quante volte si vuole (nel senso in quanti file vuoi)
Sto facendo un programma in cui ho la necessità di utilizzare più *.c file e 1 *.h file .
Il programma necessità di una variabile globale.
Mi sono imbattuto però nel problema che dichiarandola e definendola nel file *.h ( incluso in TUTTI i file *.c ) il linker mi dice che la variabile è dublicata.
Ho cercato un po' su internet e ho visto che non sono stato l'unico ad avere questo problema.
Ho trovato una soluzione ma non mi sono ben chiari alcuni concetti:
Nel file *.header la variabile è dichiarata come segue: extern int max_game_time; Perchè devo metterla extern? Cosa ottengo facendo così?
Nei due file *.c la variabile è dichiarata così: int max_game_time;
Mi sarei aspettato invece di poter dichiarare la variabile globale nel file header e porla subito uguale a 0, e lasciarla solo dichiarata negli altri due file *.c . Invece non si può... Come mai?
Detto questo, è corretto questo modo di dichiarare variabili globali in file header inclusi in più *.c file?
C'è un modo migliore?
Inoltre, cosa significano:
#ifndef _NOMEHEADER_H_
#define _NOMEHEADER_H_
...
#endif
A cosa servono?
Probabilmente la soluzione che cerchi (più semplice) è mettere la dichiarazione della variabile nello header e includere lo header nei due file sorgente in cui ne hai bisogno.
/*header.h*/
int myVar;
/*file1.c*/
#include "header.h"
[...]
void changeIt()
{
myVar = 1;
}
[...]
/*file2.c*/
#include "header.h"
[...]
int main()
{
changeIt();
printf("%d\n", myVar);
return (0);
}
[...]
Oppure in alternativa, mettere in uno header appunto la direttiva "extern" che come dice tuccio dice al compilatore di non allocare nuovo spazio per la variabile e permetterà al linker di risolvere l'indirizzo necessario.
Ricorda che alla direttiva "extern" devono seguire tutti gli attributi della variabile "originale", se per esempio dichiari la variabile "originale" con la keyword "volatile", a seguito della keyword "extern" dovrai far sempre seguire anche la keyword "volatile" (oltre al tipo e al nome della variabile). E cosi via.
L'uso delle direttive del preprocessore #ifndef SOMETHING_H
#define SOMETHING_H
#endif
meglio conosciuto come include guard serve per evitare errori in fase di compilazione come quelli che hai tu, ovvero di ritrovarsi con duplice definizione di variabili o include ciclici.
Dice al precompilatore (in fase di precompilazione e quindi quando un file deve essere incluso in un'altro file) esattamente:
- se non esiste alcuna definizione della macro SOMETHING_H
- dichiara SOMETHING_H
- includi il corpo dello header
- fine
Se dopo #endif
Dichiari qualcosa questo verrà sempre incluso ogni volta.
Anche se spesso si assume che la include guard permetta di impedire l'inclusione del file .h se la macro è già presente, questo non è corretto. Il precompilatore, infatti ogni volta include il file .h ed analizza la condizione, se la condizione è vera include il corpo, altrimenti include il file comunque ma escludendo tutto quello che c'è tra #ifndef e #endif (nel nostro caso un file vuoto se non specifichiamo altro prima di #ifndef o dopo #endif).
Alcuni compilatori supportano la direttiva #pragma once. Questa invece permette di ottimizzare la fase di precompilazione permettendo al precompilatore di sapere quando un file deve essere incluso solo una volta e attivando (se supportati) ottimizzazioni per velocizzare il processo.
Nel caso questa direttiva non sia supportata viene semplicemente ignorata (non sono mai venuto a conoscenza di compilatori che segnalano errori per una direttiva pragma non supportata).
Volendo potresti mescolare la direttiva pragma e la include guard per supportare tutti i compilatori e al tempo stesso sfruttare l'ottimizzazione dei compilatori che supportano la suddetta pragma.
esempio:
#ifndef FILE_H
#define FILE_H
#pragma once
#endif /*FILE_H*/
Beh, è ora di andare a :ronf:
Ciao!
Mi sembra di ricordare che per avere una variabile globale la devi dichiarare normalmente in uno dei file .c e in tutti gli altri dichiararla come extern. Ho la necessità che la variabile si trovi in un file H e non C
allora diciamo che questa:
int var;
è la definizione della variabile, che alloca lo spazio di cui necessita e ce ne può essere una sola
questa invece:
extern int var;
è una dichiarazione della variabile, che crea "un buco" nell'oggetto compilato che è compito del linker riempire
la definizione deve essere una sola, le dichiarazioni possono essere tante, per cui la soluzione che si utilizza di solito è inserire le dichiarazioni nei file header, che possono essere incluse quante volte si vuole (nel senso in quanti file vuoi) Ok quindi è corretto come ho fatto prima, no?
Ovvero, nell'header H
extern int variabile;
E negli altri file C:
int variabile;
Probabilmente la soluzione che cerchi (più semplice) è mettere la dichiarazione della variabile nello header e includere lo header nei due file sorgente in cui ne hai bisogno.
/*header.h*/
int myVar;
/*file1.c*/
#include "header.h"
[...]
void changeIt()
{
myVar = 1;
}
[...]
/*file2.c*/
#include "header.h"
[...]
int main()
{
changeIt();
printf("%d\n", myVar);
return (0);
}
[...]
Ci ho già provato ma questo semplice codice non funziona :p
Oppure in alternativa, mettere in uno header appunto la direttiva "extern" che come dice tuccio dice al compilatore di non allocare nuovo spazio per la variabile e permetterà al linker di risolvere l'indirizzo necessario.
Ricorda che alla direttiva "extern" devono seguire tutti gli attributi della variabile "originale", se per esempio dichiari la variabile "originale" con la keyword "volatile", a seguito della keyword "extern" dovrai far sempre seguire anche la keyword "volatile" (oltre al tipo e al nome della variabile). E cosi via. Ok, capito!
L'uso delle direttive del preprocessore #ifndef SOMETHING_H
#define SOMETHING_H
#endif
meglio conosciuto come include guard serve per evitare errori in fase di compilazione come quelli che hai tu, ovvero di ritrovarsi con duplice definizione di variabili o include ciclici.
Dice al precompilatore (in fase di precompilazione e quindi quando un file deve essere incluso in un'altro file) esattamente:
- se non esiste alcuna definizione della macro SOMETHING_H
- dichiara SOMETHING_H
- includi il corpo dello header
- fine
Se dopo #endif
Dichiari qualcosa questo verrà sempre incluso ogni volta.
Anche se spesso si assume che la include guard permetta di impedire l'inclusione del file .h se la macro è già presente, questo non è corretto. Il precompilatore, infatti ogni volta include il file .h ed analizza la condizione, se la condizione è vera include il corpo, altrimenti include il file comunque ma escludendo tutto quello che c'è tra #ifndef e #endif (nel nostro caso un file vuoto se non specifichiamo altro prima di #ifndef o dopo #endif).
Alcuni compilatori supportano la direttiva #pragma once. Questa invece permette di ottimizzare la fase di precompilazione permettendo al precompilatore di sapere quando un file deve essere incluso solo una volta e attivando (se supportati) ottimizzazioni per velocizzare il processo.
Nel caso questa direttiva non sia supportata viene semplicemente ignorata (non sono mai venuto a conoscenza di compilatori che segnalano errori per una direttiva pragma non supportata).
Volendo potresti mescolare la direttiva pragma e la include guard per supportare tutti i compilatori e al tempo stesso sfruttare l'ottimizzazione dei compilatori che supportano la suddetta pragma.
esempio:
#ifndef FILE_H
#define FILE_H
#pragma once
#endif /*FILE_H*/
Beh, è ora di andare a :ronf:
Ciao!
#ifndef _NOMEHEADER_H_
#define _NOMEHEADER_H_
...
#endif
Queste sono direttive per il preprocessore, ifndef e define in particolare vengono usate in questo modo per assicurarsi che la parte all'interno dell'if non venga interpretata dal preprocessore più di una volta.
Ma quindi non mi è chiara tutta questa spiegazione se basta scrivere nel file H:
#ifndef HEADER_H_
#define HEADER_H_
int variabile_globale;
#endif
per avere la variabile globale inclusa in entrambi i file C .
Quale è meglio? Usare extern? O l'ifndef?
wingman87
10-12-2010, 17:02
Mi sa che hai fatto un po' di confusione. Direi che tuccio ha spiegato molto chiaramente l'uso di extern, prova a rileggere meglio il suo post.
E su #ifndef e #define provo a farti un esempio.
Tanto per cominciare bisogna sapere che la direttiva #include non fa altro che sostituire l'istruzione #include con il contenuto del file specificato.
Detto questo guarda questo esempio (stupido ma esplicativo):
// file header.h
int var;
// file header2.h
#include "header.h"
// file source.c
#include "header.h"
#include "header2.h"
La compilazione di source.c fallisce perché var risulta definita 2 volte: una per via dell'inclusione di header.h e un'altra perché includendo header2.h viene reincluso anche header.h.
Usando ifndef e define:
// file header.h
#define _HEADER_
int var;
// file header2.h
#ifndef _HEADER_
#include "header.h"
#endif
// file source.c
#ifndef _HEADER_
#include "header.h"
#endif
#include "header2.h"
Il preprocessore analizza source.c e trova l'istruzione #ifndef _HEADER_. Esso non è ancora stato definito quindi viene incluso header.h, il quale definisce _HEADER_. Successivamente viene incluso header2.h ma header non viene reincluso perché _HEADER_ è definito.
Ovviamente puoi mettere #ifndef e #define un po' dove ti pare purché abbia senso, io ho messo define in header.h invece che subito dopo #ifndef.
Bene, però sia usando il metodo che ho spiegato all'inizio, sia questo dell'ifndef, ottengo lo stesso risultato, ovvero di poter dichiarare una variabile globale in un header e utilizzare lo stesso header in più file.
La mia domanda ora è: quale metodo conviene utiizzare? Quale, dal punto di vista dell'ottimizzazione, è migliore?
Ci ho già provato ma questo semplice codice non funziona :p
Ovviamente devi aggiungere ai file gli header necessari ed esportare il prototipo della funzione changeIt affinchè funzioni, io ti ho riportato solo le parti di interesse.
[edit]
La risposta alla tua domanda stà nel design della tua applicazione e come/dove usi la variabile.
wingman87
10-12-2010, 18:09
Per avere una variabile globale devi fare come ti dicevo nel mio primo post e come diceva anche tuccio. La definisci in un solo file e in tutti gli altri la dichiari con la parola chiave extern. Se poi la definisci/dichiari direttamente o indirettamente (cioè definendola/dichiarandola in un header e poi includendolo) non è rilevante, la cosa importante è che poi fai le inclusioni giuste.
Il discorso su ifndef e define è un discorso a parte.
wingman87
10-12-2010, 18:12
Ovviamente devi aggiungere ai file gli header necessari ed esportare il prototipo della funzione changeIt affinchè funzioni, io ti ho riportato solo le parti di interesse.
A me quell'esempio non sembra corretto perché file1.c e file2.c definiscono 2 istanze diverse della variabile myVar.
Per avere una variabile globale devi fare come ti dicevo nel mio primo post e come diceva anche tuccio. La definisci in un solo file e in tutti gli altri la dichiari con la parola chiave extern. Se poi la definisci/dichiari direttamente o indirettamente (cioè definendola/dichiarandola in un header e poi includendolo) non è rilevante, la cosa importante è che poi fai le inclusioni giuste.
Il discorso su ifndef e define è un discorso a parte.
Si ma continuo a non capire perchè dici che il discorso dell'ifndef è una cosa diversa, non è proprio ciò che calza per le mie neccesità?
Inoltre usando l'ifndef risolvo il problema.
Certo aggiungendo ora nel main:
extern int max_game_time;
Non da problemi ( rimane sempre il fatto che non posso definirla uguale a 0, cmq... ), però non è un surplus?
Ora, immagino funzioni uguale anche se io tolgo gli ifndef, però se si dovesse fare una comparazione, è meglio, a livello di risorse, usare l'ifndef e includere il file H dentro tutti i file C o dichiarare la variabile normalmente nel file H ( senza ifndef ) e dichiarla extern in tutti i file C?
A me quell'esempio non sembra corretto perché file1.c e file2.c definiscono 2 istanze diverse della variabile myVar.
Fai presto a provarlo. Dimmi poi se funziona o no.
A livello di "risorse" non ti cambia proprio nulla. Allochi sempre e solo una variabile.
L'IFNDEF non ha niente a che fare con la dichiarazione di una variabile, è un meccanismo di sicurezza per evitare errori dovuti ad una doppia inclusione dello stesso file header.
wingman87
10-12-2010, 22:53
Fai presto a provarlo. Dimmi poi se funziona o no.
E' vero, funziona... Allora non capisco a cosa serve extern :wtf:
Può essere per via del compilatore? Oppure è quello il comportamento corretto? Non mi convince...
Da quel che ne deduco, in questo caso non fa differenza il modo con cui si ottiene il risultato ( ovvero avere una variabile globale in un header incluso in più file C ) visto che, da quel che mi dite, le risorse allocate sono sempre uguali.
E' vero, funziona... Allora non capisco a cosa serve extern :wtf:
Può essere per via del compilatore? Oppure è quello il comportamento corretto? Non mi convince...
I compilatori ed i linker più recenti sono in grado di assumere per default che una variabile con lo stesso nome e dello stesso tipo sono riferimenti alla stessa locazione di memoria. Una sorta di auto-extern se vogliamo. Ovviamente se provi a dichiararne il valore nello header avrai un errore per ogni inclusione, un errore di duplicazione della variabile.
Se vuoi vederci meglio su linux prova a compilare con la flag -E per vedere come si comporta il precompilatore, e con -S ti puoi studiare il codice assembly prodotto, vedi che l'asm dichiara la variabile nel file del main.
Da quel che ne deduco, in questo caso non fa differenza il modo con cui si ottiene il risultato ( ovvero avere una variabile globale in un header incluso in più file C ) visto che, da quel che mi dite, le risorse allocate sono sempre uguali.
Il metodo da purista è quello di evitare la definizione di variabili nello header, ma limitarsi a dichiararla preceduta dalla keyword extern.
Il mio era un esempio sfruttabile con i recenti compilatori, se provi funzionerà anche su gcc con le flag -pedantic -ansi -Wall per il comportamento descritto sopra.
Se definisci la variabile con un valore nello header avrai un problema se importi lo header in altri file, ma se la definisci senza valore o dichiari con la keyword extern non avrai problemi (sulla multipla definizione non sò quali compilatori e da che versione la supportino, ma è una cosa che và già da almeno un paio di anni a questa parte).
E comunque sei sicuro che la variabile viene sempre e solo allocata una volta, perchè altrimenti ci sarebbe una corrispondenza dell'identificatore nella tabella dei simboli che causerebbe un errore per multipla definizione.
wingman87
11-12-2010, 16:52
I compilatori ed i linker più recenti sono in grado di assumere per default che una variabile con lo stesso nome e dello stesso tipo sono riferimenti alla stessa locazione di memoria. Una sorta di auto-extern se vogliamo.
Grazie per le informazioni. Era quello che sospettavo. Ad ogni modo non mi sembra buona pratica sfruttare questa feature del compilatore. Inoltre usando extern possiamo accorgerci di eventuali errori nel caso in cui senza accorgercene dichiariamo la variabile con un nome leggermente diverso. E possiamo anche inizializzare la variabile laddove è definita.
Grazie a tutti, tutto chiaro!
Grazie per le informazioni.
Grazie a tutti, tutto chiaro!
Di nulla =))
...quando si può fà piacere condividere.
Ciao e buona serata ad entrambi.
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.