Supponiamo che hai qualcosa di questo tipo:
C:
// main.c
#include "pippo.h"
int g = 1;
int main() {
printf("g = %d\n", g);
foo();
return 0;
}
// pippo.c
#include "pippo.h"
extern int g = 2;
void foo() {
printf("g = %d\n", g);
}
La variabile g vale 1 o 2? Chiaramente c'è qualcosa che non quadra, è come se avessi due variabili globali (in questo caso, per renderlo ancora più ovvio, addirittura con valori diversi) con lo stesso nome. La keyword extern ti serve per evitare questo problema. Serve per dire: voglio utilizzare una variabile globale che ho già definito da qualche altra parte. Il compilatore fingerà che quella variabile ci sia per davvero e il linker provvederà a completare il tutto.
Dove si trovano i files non ha importanza, la devi usare in un file .c in cui vuoi usare una una variabile globale definita in un altro file .c. Per i file .h il discorso è un po' diverso perché non vengono uniti dal linker. Quando includi un file .h il compilatore fa sostanzialmente copy&paste del suo contenuto sul file corrente, quindi non c'è bisogno di usare extern ma devi fare attenzione a non includerla (a.k.a., fare copy&paste) due o più volte, nemmeno in file diversi.
In parole povere, le variabili globali le devi definire in un file .c e gli extern puoi tranquillamente metterli nei .h. Il codice
extern int g = 2;
è sintatticamente sbagliato (non puoi assegnare il valore se usi extern) così come anche il codice
C:
int g; // variabile globale (vale 0)
g = 1; // assegnamento nel global scope
int main() { /* ... */ }
Se vuoi assegnare un valore ad una variabile globale lo devi fare o all'interno di una funzione o nel momento in cui la definisci (come nel codice sopra). Ancora meglio se non le usi proprio: le variabili globali hanno una brutta fama.