Come utilizzare extern per condividere le variabili tra i file di origine?

So che le variabili globali in C a volte hanno la parola chiave extern . Cos’è una variabile extern ? Come è la dichiarazione? Qual è il suo scopo?

Questo è legato alla condivisione di variabili tra file sorgente, ma come funziona esattamente? Dove posso usare extern ?

L’uso di extern è di pertinenza solo quando il programma che stai costruendo consiste di più file sorgente collegati tra loro, dove alcune delle variabili definite, ad esempio, nel file di origine file1.c devono essere referenziate in altri file di origine, come file2.c .

È importante capire la differenza tra la definizione di una variabile e la dichiarazione di una variabile :

  • Una variabile viene dichiarata quando il compilatore viene informato dell’esistenza di una variabile (e questo è il suo tipo); non alloca la memoria per la variabile in quel punto.
  • Una variabile viene definita quando il compilatore alloca la memoria per la variabile.

Puoi dichiarare una variabile più volte (anche se una volta è sufficiente); puoi definirlo solo una volta all’interno di un determinato ambito. Una definizione di variabile è anche una dichiarazione, ma non tutte le dichiarazioni di variabili sono definizioni.

Il modo migliore per dichiarare e definire variabili globali

Il modo pulito e affidabile per dichiarare e definire le variabili globali consiste nell’utilizzare un file di intestazione per contenere una dichiarazione extern della variabile.

L’intestazione è inclusa dall’unico file sorgente che definisce la variabile e da tutti i file sorgente che fanno riferimento alla variabile. Per ogni programma, un file sorgente (e un solo file sorgente) definisce la variabile. Allo stesso modo, un file di intestazione (e solo un file di intestazione) dovrebbe dichiarare la variabile. Il file di intestazione è cruciale; abilita il controllo incrociato tra le TU indipendenti (unità di traduzione – pensa i file di origine) e garantisce la coerenza.

Sebbene ci siano altri modi per farlo, questo metodo è semplice e affidabile. È dimostrato da file3.h , file1.c e file2.c :

file3.h

 extern int global_variable; /* Declaration of the variable */ 

file1.c

 #include "file3.h" /* Declaration made available here */ #include "prog1.h" /* Function declarations */ /* Variable defined here */ int global_variable = 37; /* Definition checked against declaration */ int increment(void) { return global_variable++; } 

file2.c

 #include "file3.h" #include "prog1.h" #include  void use_it(void) { printf("Global variable: %d\n", global_variable++); } 

Questo è il modo migliore per dichiarare e definire variabili globali.


I prossimi due file completano l’origine per prog1 :

I programmi completi mostrati usano le funzioni, quindi le dichiarazioni di funzione si sono insinuate. Sia C99 che C11 richiedono che le funzioni siano dichiarate o definite prima di essere utilizzate (mentre C90 no, per buoni motivi). Io uso la parola chiave extern di fronte alle dichiarazioni di funzione nelle intestazioni per coerenza – per far corrispondere l’ extern di fronte alle dichiarazioni variabili nelle intestazioni. Molte persone preferiscono non utilizzare extern di fronte alle dichiarazioni di funzioni; il compilatore non si cura – e in ultima analisi, nemmeno io fino a quando sei coerente, almeno all’interno di un file sorgente.

prog1.h

 extern void use_it(void); extern int increment(void); 

prog1.c

 #include "file3.h" #include "prog1.h" #include  int main(void) { use_it(); global_variable += 19; use_it(); printf("Increment: %d\n", increment()); return 0; } 
  • prog1 usa prog1.c , file1.c , file2.c , file3.h e prog1.h .

Il file prog1.mk è un makefile solo per prog1 . Funzionerà con la maggior parte delle versioni del prodotto prodotte da circa l’inizio del millennio. Non è legato specificamente a GNU Make.

prog1.mk

 # Minimal makefile for prog1 PROGRAM = prog1 FILES.c = prog1.c file1.c file2.c FILES.h = prog1.h file3.h FILES.o = ${FILES.c:.c=.o} CC = gcc SFLAGS = -std=c11 GFLAGS = -g OFLAGS = -O3 WFLAG1 = -Wall WFLAG2 = -Wextra WFLAG3 = -Werror WFLAG4 = -Wstrict-prototypes WFLAG5 = -Wmissing-prototypes WFLAGS = ${WFLAG1} ${WFLAG2} ${WFLAG3} ${WFLAG4} ${WFLAG5} UFLAGS = # Set on command line only CFLAGS = ${SFLAGS} ${GFLAGS} ${OFLAGS} ${WFLAGS} ${UFLAGS} LDFLAGS = LDLIBS = all: ${PROGRAM} ${PROGRAM}: ${FILES.o} ${CC} -o [email protected] ${CFLAGS} ${FILES.o} ${LDFLAGS} ${LDLIBS} prog1.o: ${FILES.h} file1.o: ${FILES.h} file2.o: ${FILES.h} # If it exists, prog1.dSYM is a directory on macOS DEBRIS = a.out core *~ *.dSYM RM_FR = rm -fr clean: ${RM_FR} ${FILES.o} ${PROGRAM} ${DEBRIS} 

Linee guida

Regole che devono essere risolte solo da esperti, e solo a ragione:

  • Un file di intestazione contiene solo dichiarazioni extern di variabili, mai definizioni di variabili static o non qualificate.
  • Per ogni variabile data, solo un file di intestazione lo dichiara (SPOT – Single Point of Truth).
  • Un file sorgente non contiene mai dichiarazioni di variabili extern – i file di origine includono sempre l’intestazione (sola) che li dichiara.
  • Per ogni variabile data, esattamente un file sorgente definisce la variabile, preferibilmente inizializzandola anch’essa. (Anche se non è necessario inizializzare esplicitamente a zero, non fa male e può fare del bene, perché in un programma può esserci solo una definizione inizializzata di una particolare variabile globale).
  • Il file sorgente che definisce la variabile include anche l’intestazione per garantire che la definizione e la dichiarazione siano coerenti.
  • Una funzione non dovrebbe mai aver bisogno di dichiarare una variabile usando extern .
  • Evita le variabili globali quando ansible, usa invece le funzioni.

Il codice sorgente e il testo di questa risposta sono disponibili nel mio repository SOQ (Stack Overflow Questions) su GitHub nella sottodirectory src / so-0143-3204 .

Se non sei un programmatore C esperto, potresti (e forse dovrebbe) smettere di leggere qui.

Non è un ottimo modo per definire variabili globali

Con alcuni (in effetti, molti) compilatori C, puoi fare a meno di quella che viene definita una definizione “comune” di una variabile. ‘Comune’, qui, fa riferimento a una tecnica utilizzata in Fortran per la condivisione di variabili tra file di origine, utilizzando un blocco COMMON (possibilmente denominato). Quello che succede qui è che ognuno di un certo numero di file fornisce una definizione provvisoria della variabile. Finché non più di un file fornisce una definizione inizializzata, i vari file finiscono per condividere una singola definizione comune della variabile:

file10.c

 #include "prog2.h" int i; /* Do not do this in portable code */ void inc(void) { i++; } 

file11.c

 #include "prog2.h" int i; /* Do not do this in portable code */ void dec(void) { i--; } 

file12.c

 #include "prog2.h" #include  int i = 9; /* Do not do this in portable code */ void put(void) { printf("i = %d\n", i); } 

Questa tecnica non è conforms alla lettera dello standard C e alla “regola della definizione unica” – è un comportamento ufficialmente non definito:

J.2 Comportamento indefinito

Viene utilizzato un identificatore con collegamento esterno, ma nel programma non esiste esattamente una definizione esterna per l’identificatore, oppure l’identificatore non viene utilizzato e esistono più definizioni esterne per l’identificatore (6.9).

§6.9 Definizioni esterne ¶5

Una definizione esterna è una dichiarazione esterna che è anche una definizione di una funzione (diversa da una definizione in linea) o un object. Se un identificatore dichiarato con il collegamento esterno viene utilizzato in un’espressione (diversa da come parte dell’operando di un operatore sizeof o _Alignof cui risultato è una costante intera), da qualche parte nell’intero programma ci deve essere esattamente una definizione esterna per l’identificatore; altrimenti, non ci deve essere più di uno. 161)

161) Pertanto, se un identificatore dichiarato con collegamento esterno non viene utilizzato in un’espressione, non è necessario che esista una definizione esterna.

Tuttavia, lo standard C lo elenca anche nell’Allegato J informativo come una delle estensioni comuni .

J.5.11 Definizioni esterne multiple

Potrebbero esserci più di una definizione esterna per l’identificatore di un object, con o senza l’uso esplicito della parola chiave extern; se le definizioni non sono d’accordo, o più di una è inizializzata, il comportamento non è definito (6.9.2).

Poiché questa tecnica non è sempre supportata, è meglio evitare di usarla, soprattutto se il tuo codice deve essere portatile . Usando questa tecnica, puoi anche finire con la punzonatura di tipo non intenzionale. Se uno dei file dichiarava come double invece di int , i linker non sicuri del tipo C probabilmente non individuerebbero la mancata corrispondenza. Se sei su una macchina con int e 64 bit bit, non riceverai nemmeno un avviso; su una macchina con 32-bit int e 64-bit double , si otterrà probabilmente un avvertimento sulle diverse dimensioni: il linker userebbe la dimensione maggiore, esattamente come un programma Fortran prenderebbe la dimensione più grande di qualsiasi blocco comune.


I prossimi due file completano la fonte per prog2 :

prog2.h

 extern void dec(void); extern void put(void); extern void inc(void); 

prog2.c

 #include "prog2.h" #include  int main(void) { inc(); put(); dec(); put(); dec(); put(); } 
  • prog2 usa prog2.c , file10.c , file11.c , file12.c , prog2.h .

avvertimento

Come notato qui nei commenti, e come affermato nella mia risposta a una domanda simile, l’uso di più definizioni per una variabile globale porta a comportamenti non definiti (J.2; §6.9), che è il modo standard per dire “qualsiasi cosa potrebbe accadere”. Una delle cose che possono accadere è che il programma si comporti come ci si aspetta; e J.5.11 dice, approssimativamente, “potresti essere fortunato più spesso di quanto meriti”. Ma un programma che si basa su più definizioni di una variabile esterna – con o senza la parola chiave ‘extern’ esplicita – non è un programma strettamente conforms e non è garantito che funzioni ovunque. Equivalentemente: contiene un bug che può o non può mostrare se stesso.

Violare le linee guida

Ci sono, naturalmente, molti modi in cui queste linee guida possono essere violate. Occasionalmente, ci può essere una buona ragione per rompere le linee guida, ma tali occasioni sono estremamente insolite.

faulty_header.h

 int some_var; /* Do not do this in a header!!! */ 

Nota 1: se l’intestazione definisce la variabile senza la parola chiave extern , ogni file che include l’intestazione crea una definizione provvisoria della variabile. Come notato in precedenza, questo spesso funziona, ma lo standard C non garantisce che funzionerà.

broken_header.h

 int some_var = 13; /* Only one source file in a program can use this */ 

Nota 2: se l’intestazione definisce e inizializza la variabile, solo un file sorgente in un dato programma può usare l’intestazione. Dato che le intestazioni sono principalmente per la condivisione di informazioni, è un po ‘sciocco crearne una che possa essere utilizzata solo una volta.

seldom_correct.h

 static int hidden_global = 3; /* Each source file gets its own copy */ 

Nota 3: se l’intestazione definisce una variabile statica (con o senza inizializzazione), allora ogni file sorgente finisce con la sua versione privata della variabile ‘globale’.

Se la variabile è in realtà una matrice complessa, ad esempio, ciò può portare a una duplicazione estrema del codice. Può, molto raramente, essere un modo sensato per ottenere qualche effetto, ma è molto insolito.


Sommario

Usa la tecnica dell’intestazione che ho mostrato per prima. Funziona in modo affidabile e ovunque. Si noti, in particolare, che l’intestazione che dichiara la global_variable è inclusa in ogni file che la utilizza, inclusa quella che la definisce. Questo assicura che tutto sia auto-consistente.

Preoccupazioni simili sorgono con la dichiarazione e la definizione delle funzioni: si applicano regole analoghe. Ma la domanda riguardava specificamente le variabili, quindi ho mantenuto solo la risposta alle variabili.

Fine della risposta originale

Se non sei un programmatore C esperto, probabilmente dovresti smettere di leggere qui.


Aggiunta tardiva maggiore

Evitare la duplicazione del codice

Una preoccupazione che a volte viene sollevata (e legittimamente) riguardo alle “dichiarazioni nelle intestazioni, il meccanismo delle definizioni nel sorgente” qui descritto è che ci sono due file da mantenere sincronizzati: l’intestazione e la fonte. Di solito questo è seguito dall’osservazione che una macro può essere usata in modo che l’intestazione funzioni in doppia funzione, normalmente dichiarando le variabili, ma quando una macro specifica viene impostata prima che l’intestazione sia inclusa, definisce invece le variabili.

Un’altra preoccupazione può essere che le variabili debbano essere definite in ciascuno di un certo numero di “programmi principali”. Questa è normalmente una preoccupazione spuria; puoi semplicemente introdurre un file sorgente C per definire le variabili e colbind il file object prodotto con ciascuno dei programmi.

Uno schema tipico funziona così, usando la variabile globale originale illustrata in file3.h :

file3a.h

 #ifdef DEFINE_VARIABLES #define EXTERN /* nothing */ #else #define EXTERN extern #endif /* DEFINE_VARIABLES */ EXTERN int global_variable; 

file1a.c

 #define DEFINE_VARIABLES #include "file3a.h" /* Variable defined - but not initialized */ #include "prog3.h" int increment(void) { return global_variable++; } 

file2a.c

 #include "file3a.h" #include "prog3.h" #include  void use_it(void) { printf("Global variable: %d\n", global_variable++); } 

I prossimi due file completano l’origine per prog3 :

prog3.h

 extern void use_it(void); extern int increment(void); 

prog3.c

 #include "file3a.h" #include "prog3.h" #include  int main(void) { use_it(); global_variable += 19; use_it(); printf("Increment: %d\n", increment()); return 0; } 
  • prog3 usa prog3.c , file1a.c , file2a.c , file3a.h , prog3.h .

Inizializzazione variabile

Il problema con questo schema come mostrato è che non fornisce l’inizializzazione della variabile globale. Con C99 o C11 e elenchi di argomenti variabili per macro, è ansible definire una macro per supportare anche l’inizializzazione. (Con C89 e nessun supporto per gli elenchi di argomenti variabili nelle macro, non esiste un modo semplice per gestire inizializzatori arbitrariamente lunghi.)

file3b.h

 #ifdef DEFINE_VARIABLES #define EXTERN /* nothing */ #define INITIALIZER(...) = __VA_ARGS__ #else #define EXTERN extern #define INITIALIZER(...) /* nothing */ #endif /* DEFINE_VARIABLES */ EXTERN int global_variable INITIALIZER(37); EXTERN struct { int a; int b; } oddball_struct INITIALIZER({ 41, 43 }); 

Invertire i contenuti dei blocchi #if e #else , #else bug identificati da Denis Kniazhev

file1b.c

 #define DEFINE_VARIABLES #include "file3b.h" /* Variables now defined and initialized */ #include "prog4.h" int increment(void) { return global_variable++; } int oddball_value(void) { return oddball_struct.a + oddball_struct.b; } 

file2b.c

 #include "file3b.h" #include "prog4.h" #include  void use_them(void) { printf("Global variable: %d\n", global_variable++); oddball_struct.a += global_variable; oddball_struct.b -= global_variable / 2; } 

Chiaramente, il codice per la struttura di oddball non è quello che normalmente si scrive, ma illustra il punto. Il primo argomento della seconda invocazione di INITIALIZER è { 41 e l’argomento rimanente (singolare in questo esempio) è 43 } . Senza C99 o supporto simile per gli elenchi di argomenti variabili per le macro, gli inizializzatori che devono contenere virgole sono molto problematici.

Corretta l’intestazione file3b.h inclusa (invece di fileba.h ) per Denis Kniazhev


I prossimi due file completano l’origine per prog4 :

prog4.h

 extern int increment(void); extern int oddball_value(void); extern void use_them(void); 

prog4.c

 #include "file3b.h" #include "prog4.h" #include  int main(void) { use_them(); global_variable += 19; use_them(); printf("Increment: %d\n", increment()); printf("Oddball: %d\n", oddball_value()); return 0; } 
  • prog4 usa prog4.c , file1b.c , file2b.c , prog4.h , file3b.h .

Header Guards

Qualsiasi intestazione deve essere protetta contro la reinclusione, in modo che le definizioni di tipo (enum, struct o union types o typedefs in genere) non causino problemi. La tecnica standard è quella di avvolgere il corpo dell’intestazione in una protezione dell’intestazione come:

 #ifndef FILE3B_H_INCLUDED #define FILE3B_H_INCLUDED ...contents of header... #endif /* FILE3B_H_INCLUDED */ 

L’intestazione potrebbe essere inclusa due volte indirettamente. Ad esempio, se file4b.h include file3b.h per una definizione di tipo che non è mostrata, e file1b.c deve utilizzare sia l’header file4b.h che file3b.h , allora hai alcuni problemi più difficili da risolvere. Chiaramente, potresti rivedere l’elenco delle intestazioni per includere solo file4b.h . Tuttavia, potresti non essere a conoscenza delle dipendenze interne e il codice dovrebbe, idealmente, continuare a funzionare.

Inoltre, inizia a diventare complicato perché potresti includere file4b.h prima di includere file3b.h per generare le definizioni, ma le normali protezioni di intestazione su file3b.h impedirebbero che l’intestazione venisse reinclusa.

Quindi, è necessario includere il corpo di file3b.h al massimo una volta per le dichiarazioni e al massimo una volta per le definizioni, ma potrebbe essere necessario entrambi in una singola unità di traduzione (TU – una combinazione di un file sorgente e le intestazioni che utilizza) .

Inclusione multipla con definizioni variabili

Tuttavia, può essere sottoposto a un vincolo non troppo irragionevole. Introduciamo un nuovo set di nomi di file:

  • external.h per le definizioni di macro EXTERN, ecc.
  • file1c.h per definire i tipi (in particolare, struct oddball , il tipo di oddball_struct ).
  • file2c.h per definire o dichiarare le variabili globali.
  • file3c.c che definisce le variabili globali.
  • file4c.c che utilizza semplicemente le variabili globali.
  • file5c.c che mostra che è ansible dichiarare e quindi definire le variabili globali.
  • file6c.c che mostra che è ansible definire e quindi (tentare di) dichiarare le variabili globali.

In questi esempi, file5c.c e file6c.c includono direttamente l’intestazione file2c.h più volte, ma questo è il modo più semplice per mostrare che il meccanismo funziona. Significa che se l’intestazione fosse inclusa indirettamente due volte, sarebbe anche sicuro.

Le restrizioni per questo lavoro sono:

  1. L’intestazione che definisce o dichiara le variabili globali potrebbe non definire esso stesso alcun tipo.
  2. Immediatamente prima di includere un’intestazione che dovrebbe definire le variabili, si definisce la macro DEFINE_VARIABLES.
  3. L’intestazione che definisce o dichiara le variabili ha contenuti stilizzati.

external.h

 /* ** This header must not contain header guards (like  must not). ** Each time it is invoked, it redefines the macros EXTERN, INITIALIZE ** based on whether macro DEFINE_VARIABLES is currently defined. */ #undef EXTERN #undef INITIALIZE #ifdef DEFINE_VARIABLES #define EXTERN /* nothing */ #define INITIALIZE(...) = __VA_ARGS__ #else #define EXTERN extern #define INITIALIZE(...) /* nothing */ #endif /* DEFINE_VARIABLES */ 

file1c.h

 #ifndef FILE1C_H_INCLUDED #define FILE1C_H_INCLUDED struct oddball { int a; int b; }; extern void use_them(void); extern int increment(void); extern int oddball_value(void); #endif /* FILE1C_H_INCLUDED */ 

file2c.h

 /* Standard prologue */ #if defined(DEFINE_VARIABLES) && !defined(FILE2C_H_DEFINITIONS) #undef FILE2C_H_INCLUDED #endif #ifndef FILE2C_H_INCLUDED #define FILE2C_H_INCLUDED #include "external.h" /* Support macros EXTERN, INITIALIZE */ #include "file1c.h" /* Type definition for struct oddball */ #if !defined(DEFINE_VARIABLES) || !defined(FILE2C_H_DEFINITIONS) /* Global variable declarations / definitions */ EXTERN int global_variable INITIALIZE(37); EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 }); #endif /* !DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS */ /* Standard epilogue */ #ifdef DEFINE_VARIABLES #define FILE2C_H_DEFINITIONS #endif /* DEFINE_VARIABLES */ #endif /* FILE2C_H_INCLUDED */ 

file3c.c

 #define DEFINE_VARIABLES #include "file2c.h" /* Variables now defined and initialized */ int increment(void) { return global_variable++; } int oddball_value(void) { return oddball_struct.a + oddball_struct.b; } 

file4c.c

 #include "file2c.h" #include  void use_them(void) { printf("Global variable: %d\n", global_variable++); oddball_struct.a += global_variable; oddball_struct.b -= global_variable / 2; } 

file5c.c

 #include "file2c.h" /* Declare variables */ #define DEFINE_VARIABLES #include "file2c.h" /* Variables now defined and initialized */ int increment(void) { return global_variable++; } int oddball_value(void) { return oddball_struct.a + oddball_struct.b; } 

file6c.c

 #define DEFINE_VARIABLES #include "file2c.h" /* Variables now defined and initialized */ #include "file2c.h" /* Declare variables */ int increment(void) { return global_variable++; } int oddball_value(void) { return oddball_struct.a + oddball_struct.b; } 

Il prossimo file sorgente completa la fonte (fornisce un programma principale) per prog5 , prog6 e prog7 :

prog5.c

 #include "file2c.h" #include  int main(void) { use_them(); global_variable += 19; use_them(); printf("Increment: %d\n", increment()); printf("Oddball: %d\n", oddball_value()); return 0; } 
  • prog5 usa prog5.c , file3c.c , file4c.c , file1c.h , file2c.h , external.h .
  • prog6 usa prog5.c , file5c.c , file4c.c , file1c.h , file2c.h , external.h .
  • prog7 usa prog5.c , file6c.c , file4c.c , file1c.h , file2c.h , external.h .

Questo schema evita la maggior parte dei problemi. Si incontra un problema solo se un’intestazione che definisce variabili (come file2c.h ) è inclusa da un’altra intestazione (ad esempio file7c.h ) che definisce le variabili. Non c’è un modo semplice per farlo a parte “non farlo”.

Puoi parzialmente aggirare il problema revisionando file2c.h in file2d.h :

file2d.h

 /* Standard prologue */ #if defined(DEFINE_VARIABLES) && !defined(FILE2D_H_DEFINITIONS) #undef FILE2D_H_INCLUDED #endif #ifndef FILE2D_H_INCLUDED #define FILE2D_H_INCLUDED #include "external.h" /* Support macros EXTERN, INITIALIZE */ #include "file1c.h" /* Type definition for struct oddball */ #if !defined(DEFINE_VARIABLES) || !defined(FILE2D_H_DEFINITIONS) /* Global variable declarations / definitions */ EXTERN int global_variable INITIALIZE(37); EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 }); #endif /* !DEFINE_VARIABLES || !FILE2D_H_DEFINITIONS */ /* Standard epilogue */ #ifdef DEFINE_VARIABLES #define FILE2D_H_DEFINITIONS #undef DEFINE_VARIABLES #endif /* DEFINE_VARIABLES */ #endif /* FILE2D_H_INCLUDED */ 

Il problema diventa “se l’intestazione include #undef DEFINE_VARIABLES ?” Se lo ometti dall’intestazione e avvolgi qualsiasi invocazione di definizione con #define e #undef :

 #define DEFINE_VARIABLES #include "file2c.h" #undef DEFINE_VARIABLES 

nel codice sorgente (quindi le intestazioni non modificano mai il valore di DEFINE_VARIABLES ), quindi dovresti essere pulito. È solo una seccatura dover ricordare di scrivere la linea in più. Un’alternativa potrebbe essere:

 #define HEADER_DEFINING_VARIABLES "file2c.h" #include "externdef.h" 

externdef.h

 /* ** This header must not contain header guards (like  must not). ** Each time it is included, the macro HEADER_DEFINING_VARIABLES should ** be defined with the name (in quotes - or possibly angle brackets) of ** the header to be included that defines variables when the macro ** DEFINE_VARIABLES is defined. See also: external.h (which uses ** DEFINE_VARIABLES and defines macros EXTERN and INITIALIZE ** appropriately). ** ** #define HEADER_DEFINING_VARIABLES "file2c.h" ** #include "externdef.h" */ #if defined(HEADER_DEFINING_VARIABLES) #define DEFINE_VARIABLES #include HEADER_DEFINING_VARIABLES #undef DEFINE_VARIABLES #undef HEADER_DEFINING_VARIABLES #endif /* HEADER_DEFINING_VARIABLES */ 

Questo sta diventando un po ‘contorto, ma sembra essere sicuro (usando il file2d.h , senza #undef DEFINE_VARIABLES nel file2d.h ).

file7c.c

 /* Declare variables */ #include "file2d.h" /* Define variables */ #define HEADER_DEFINING_VARIABLES "file2d.h" #include "externdef.h" /* Declare variables - again */ #include "file2d.h" /* Define variables - again */ #define HEADER_DEFINING_VARIABLES "file2d.h" #include "externdef.h" int increment(void) { return global_variable++; } int oddball_value(void) { return oddball_struct.a + oddball_struct.b; } 

file8c.h

 /* Standard prologue */ #if defined(DEFINE_VARIABLES) && !defined(FILE8C_H_DEFINITIONS) #undef FILE8C_H_INCLUDED #endif #ifndef FILE8C_H_INCLUDED #define FILE8C_H_INCLUDED #include "external.h" /* Support macros EXTERN, INITIALIZE */ #include "file2d.h" /* struct oddball */ #if !defined(DEFINE_VARIABLES) || !defined(FILE8C_H_DEFINITIONS) /* Global variable declarations / definitions */ EXTERN struct oddball another INITIALIZE({ 14, 34 }); #endif /* !DEFINE_VARIABLES || !FILE8C_H_DEFINITIONS */ /* Standard epilogue */ #ifdef DEFINE_VARIABLES #define FILE8C_H_DEFINITIONS #endif /* DEFINE_VARIABLES */ #endif /* FILE8C_H_INCLUDED */ 

file8c.c

 /* Define variables */ #define HEADER_DEFINING_VARIABLES "file2d.h" #include "externdef.h" /* Define variables */ #define HEADER_DEFINING_VARIABLES "file8c.h" #include "externdef.h" int increment(void) { return global_variable++; } int oddball_value(void) { return oddball_struct.a + oddball_struct.b; } 

I prossimi due file completano l’origine per prog8 e prog9 :

prog8.c

 #include "file2d.h" #include  int main(void) { use_them(); global_variable += 19; use_them(); printf("Increment: %d\n", increment()); printf("Oddball: %d\n", oddball_value()); return 0; } 

file9c.c

 #include "file2d.h" #include  void use_them(void) { printf("Global variable: %d\n", global_variable++); oddball_struct.a += global_variable; oddball_struct.b -= global_variable / 2; } 
  • prog8 usa prog8.c , file7c.c , file9c.c .
  • prog9 usa prog8.c , file8c.c , file9c.c .

Tuttavia, i problemi sono relativamente improbabili che si verifichino nella pratica, specialmente se si presta il consiglio standard

Evita le variabili globali


Questa esposizione manca qualcosa?

Confessione : lo schema del “codice duplicato evitabile” delineato qui è stato sviluppato perché il problema riguarda il codice su cui lavoro (ma non lo possiedo) ed è una preoccupazione sconcertante per lo schema delineato nella prima parte della risposta. Tuttavia, lo schema originale ti lascia solo due posizioni da modificare per mantenere sincronizzate le definizioni e le dichiarazioni delle variabili, il che è un grande passo in avanti rispetto alla presenza di dichiarazioni exernal variabili sparse per il codice base (che conta davvero quando ci sono migliaia di file in totale) . Tuttavia, il codice nei file con i nomi fileNc.[ch] (più external.h e externdef.h ) mostra che può essere fatto funzionare. Chiaramente, non sarebbe difficile creare uno script di generatore di intestazione per fornire il modello standardizzato per una variabile che definisce e dichiara il file di intestazione.

NB Questi sono programmi giocattolo con un codice appena sufficiente da renderli marginalmente interessanti. C’è una ripetizione all’interno degli esempi che potrebbero essere rimossi, ma non è per semplificare la spiegazione pedagogica. (Ad esempio: la differenza tra prog5.c e prog8.c è il nome di una delle intestazioni incluse. Sarebbe ansible riorganizzare il codice in modo che la funzione main() non sia stata ripetuta, ma occulterebbe di più di quanto rivelato).

Una variabile extern è una dichiarazione (grazie a sbi per la correzione) di una variabile definita in un’altra unità di traduzione. Ciò significa che l’archiviazione per la variabile viene allocata in un altro file.

Supponiamo tu abbia due file .c test1.c e test2.c . Se si definisce una variabile globale int test1_var; in test1.c e si desidera accedere a questa variabile in test2.c è necessario utilizzare extern int test1_var; in test2.c .

Campione completo:

 $ cat test1.c int test1_var = 5; $ cat test2.c #include  extern int test1_var; int main(void) { printf("test1_var = %d\n", test1_var); return 0; } $ gcc test1.c test2.c -o test $ ./test test1_var = 5 

Extern è la parola chiave che si utilizza per dichiarare che la variabile si trova in un’altra unità di traduzione.

Quindi puoi decidere di usare una variabile in un’unità di traduzione e poi accedervi da un’altra, poi nella seconda la dichiari come extern e il simbolo verrà risolto dal linker.

Se non lo dichiari come extern otterrai 2 variabili uguali ma non correlate e un errore di più definizioni della variabile.

Mi piace pensare a una variabile esterna come una promise che fai al compilatore.

Quando incontra un extern, il compilatore può solo scoprire il suo tipo, non dove “vive”, quindi non può risolvere il riferimento.

Stai dicendo: “Fidati di me: al momento del collegamento questo riferimento sarà risolvibile”.

extern dice al compilatore di fidarsi di te che la memoria di questa variabile è dichiarata altrove, quindi non tenta di allocare / controllare la memoria.

Pertanto, è ansible compilare un file che ha riferimento a un extern, ma non è ansible collegarlo se tale memoria non viene dichiarata da qualche parte.

Utile per variabili globali e librerie, ma pericoloso perché il linker non scrive check.

L’aggiunta di un extern trasforma una definizione di variabile in una dichiarazione di variabile. Vedi questo thread su quale sia la differenza tra una dichiarazione e una definizione.

L’interpretazione corretta di extern è che tu dici qualcosa al compilatore. Dite al compilatore che, nonostante non sia presente in questo momento, la variabile dichiarata verrà in qualche modo trovata dal linker (tipicamente in un altro object (file)). Il linker sarà quindi il fortunato a trovare tutto e metterlo insieme, indipendentemente dal fatto che tu abbia o meno delle dichiarazioni extern.

In C una variabile all’interno di un file dice example.c ha un ambito locale. The compiler expects that the variable would have its definition inside the same file example.c and when it does not find the same , it would throw an error.A function on the other hand has by default global scope . Thus you do not have to explicitly mention to the compiler “look dude…you might find the definition of this function here”. For a function including the file which contains its declaration is enough.(The file which you actually call a header file). For example consider the following 2 files :
example.c

 #include extern int a; main(){ printf("The value of a is <%d>\n",a); } 

example1.c

 int a = 5; 

Now when you compile the two files together, using the following commands :

step 1)cc -o ex example.c example1.c step 2)./ex

You get the following output : The value of a is <5>

extern keyword is used with the variable for its identification as a global variable.

It also represents that you can use the variable declared using extern keyword in any file though it is declared/defined in other file.

GCC ELF Linux implementation

main.c :

 #include  int not_extern_int = 1; extern int extern_int; void main() { printf("%d\n", not_extern_int); printf("%d\n", extern_int); } 

Compilare e decompilare:

 gcc -c main.c readelf -s main.o 

L’output contiene:

 Num: Value Size Type Bind Vis Ndx Name 9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 not_extern_int 12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND extern_int 

The System V ABI Update ELF spec “Symbol Table” chapter explains:

SHN_UNDEF This section table index means the symbol is undefined. When the link editor combines this object file with another that defines the indicated symbol, this file’s references to the symbol will be linked to the actual definition.

which is basically the behavior the C standard gives to extern variables.

From now on, it is the job of the linker to make the final program, but the extern information has already been extracted from the source code into the object file.

Tested on GCC 4.8.

First off, the extern keyword is not used for defining a variable; rather it is used for declaring a variable. I can say extern is a storage class, not a data type.

extern is used to let other C files or external components know this variable is already defined somewhere. Example: if you are building a library, no need to define global variable mandatorily somewhere in library itself. The library will be compiled directly, but while linking the file, it checks for the definition.

extern allows one module of your program to access a global variable or function declared in another module of your program. You usually have extern variables declared in header files.

If you don’t want a program to access your variables or functions, you use static which tells the compiler that this variable or function cannot be used outside of this module.

extern is used so one first.c file can have full access to a global parameter in another second.c file.

The extern can be declared in the first.c file or in any of the header files first.c includes.

extern simply means a variable is defined elsewhere (eg, in another file).