Come utilizzare correttamente la parola chiave extern in C


236

La mia domanda riguarda quando una funzione deve essere referenziata con la externparola chiave in C.

Non riesco a vedere quando questo dovrebbe essere usato in pratica. Mentre scrivo un programma, tutte le funzioni che utilizzo sono rese disponibili attraverso i file di intestazione che ho incluso. Quindi, perché sarebbe utile externottenere l'accesso a qualcosa che non è stato esposto nel file di intestazione?

Potrei pensare a come externfunziona in modo errato, e in tal caso, per favore, correggimi.

Modifica: dovresti externqualcosa quando è la dichiarazione predefinita senza la parola chiave in un file di intestazione?


Risposte:


290

" extern" cambia il collegamento. Con la parola chiave, si presume che la funzione / variabile sia disponibile altrove e la risoluzione è rinviata al linker.

C'è una differenza tra "extern" sulle funzioni e sulle variabili: sulle variabili non crea un'istanza della variabile stessa, ovvero non alloca memoria. Questo deve essere fatto altrove. Pertanto è importante se si desidera importare la variabile da qualche altra parte. Per le funzioni, questo dice solo al compilatore che il collegamento è esterno. Poiché questo è il valore predefinito (si utilizza la parola chiave "statico" per indicare che una funzione non è associata utilizzando il collegamento esterno) non è necessario utilizzarla in modo esplicito.


1
allora perché la stessa cosa esterna c'è in Git: un software molto popolare e moderno controlla: github.com/git/git/blob/master/strbuf.h
rsjethani

K&R non nota che è predefinito dichiarare funzione come "esterno", tuttavia questa risposta risolve la mia confusione!
acgtyrant il

@rsjethani Penso che sia per rendere il documento più rigoroso e formattato.
acgtyrant il

Forse una domanda stupida, ma come si confronta con la dichiarazione anticipata?
weberc2,

197

extern dice al compilatore che questi dati sono definiti da qualche parte e saranno collegati con il linker.

Con l'aiuto delle risposte qui e parlare con alcuni amici qui è l'esempio pratico di un uso di extern .

Esempio 1 - per mostrare una trappola:

File stdio.h:

int errno;
/* other stuff...*/

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

Se myCFile1.o e myCFile2.o sono collegati, ognuno dei file c ha copie separate di errno . Questo è un problema poiché lo stesso errno dovrebbe essere disponibile in tutti i file collegati.

Esempio 2 - La correzione.

File stdio.h:

extern int errno;
/* other stuff...*/

File stdio.c

int errno;

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

Ora, se sia myCFile1.o che MyCFile2.o sono collegati dal linker, entrambi indicheranno lo stesso errno . Pertanto, risolvendo l'implementazione con extern .


71
Il problema non è che i moduli myCFile1 e myCFile2 hanno una copia separata di errno, è che entrambi espongono un simbolo chiamato "errno". Quando il linker lo vede, non sa quale "errno" scegliere, quindi salverà con un messaggio di errore.
cwick,

2
cosa significa "linkato dal linker"? tutti usano questo termine, non trovo alcuna definizione :(
Marcel Falliere

7
@MarcelFalliere Wiki ~ Il compilatore compila da solo ogni file sorgente e crea un file oggetto per ogni file sorgente. Linker collega questi file oggetto a 1 eseguibile.
Bitterblue,

1
@cwick gcc non sta dando un errore o un avviso anche dopo aver usato -Walle -pedantic. Perché ? e come ?
b-ak

6
Una guardia di inclusione non protegge da questa cosa esatta?
obskyr,

32

È già stato affermato che la externparola chiave è ridondante per le funzioni.

Per quanto riguarda le variabili condivise tra le unità di compilazione, è necessario dichiararle in un file di intestazione con la parola chiave extern, quindi definirle in un singolo file di origine, senza la parola chiave extern. Il file di origine singolo dovrebbe essere quello che condivide il nome del file di intestazione, per la migliore pratica.


@aib "ridondante per funzioni", controlla il mio commento nella risposta di bluebrother.
rsjethani,

Cosa succede se non si desidera esporre nessuna delle funzioni nel file di intestazione? Non sarebbe meglio dichiarare la variabile in un file C e accedervi da extern in un altro; lascia che il linker risolva il problema e nascondi il resto dell'intestazione.
ste3e,

16

Molti anni dopo, scopro questa domanda. Dopo aver letto ogni risposta e commento, ho pensato di poter chiarire alcuni dettagli ... Questo potrebbe essere utile per le persone che arrivano qui tramite la ricerca di Google.

La domanda riguarda in particolare l'uso delle funzioni "extern", quindi ignorerò l'uso di "extern" con le variabili globali.

Definiamo 3 prototipi di funzioni:

//--------------------------------------
//Filename: "my_project.H"
extern int function_1(void);
static int function_2(void);
       int function_3(void);

Il file di intestazione può essere utilizzato dal codice sorgente principale come segue:

//--------------------------------------
//Filename: "my_project.C"
#include "my_project.H"

void main(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 1234;

Per compilare e collegare, dobbiamo definire "function_2" nello stesso file di codice sorgente in cui chiamiamo quella funzione. Le altre due funzioni potrebbero essere definite in diversi codici sorgente " .C" oppure potrebbero trovarsi in qualsiasi file binario ( .OBJ, * .LIB, * .DLL), per il quale potremmo non avere il codice sorgente.

Includiamo di nuovo l'intestazione "my_project.H" in un diverso file "* .C" per comprendere meglio la differenza. Nello stesso progetto, aggiungiamo il seguente file:

//--------------------------------------
//Filename: "my_big_project_splitted.C"
#include "my_project.H"

void old_main_test(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 5678;

int function_1(void) return 12;
int function_3(void) return 34;

Caratteristiche importanti da notare:

  • Quando una funzione viene definita "statica" in un file di intestazione, il compilatore / linker deve trovare un'istanza di una funzione con quel nome in ciascun modulo che utilizza quel file di inclusione.

  • Una funzione che fa parte della libreria C può essere sostituita in un solo modulo ridefinendo un prototipo con "statico" solo in quel modulo. Ad esempio, sostituire qualsiasi chiamata a "malloc" e "free" per aggiungere la funzione di rilevamento delle perdite di memoria.

  • Lo specificatore "extern" non è realmente necessario per le funzioni. Quando non viene trovato "statico", si presume sempre che una funzione sia "esterna".

  • Tuttavia, "extern" non è l'impostazione predefinita per le variabili. Normalmente, qualsiasi file di intestazione che definisce le variabili per essere visibili su molti moduli deve usare "extern". L'unica eccezione sarebbe se un file di intestazione è garantito per essere incluso da uno e un solo modulo.

    Molti project manager richiedono quindi che tale variabile venga posizionata all'inizio del modulo, non all'interno di alcun file di intestazione. Alcuni grandi progetti, come l'emulatore di videogiochi "Mame", richiedono anche che tale variabile appaia solo sopra la prima funzione che li utilizza.


Quindi, perché una funzione statica ha bisogno di una definizione rispetto a quelle esterne? (So ​​che è in ritardo di 2 anni, ma in realtà è molto utile per la comprensione)
SubLock69

2
La definizione è necessaria se si chiama la funzione alla riga 100 e la si istanzia alla riga 500. La riga 100 dichiarerebbe un prototipo indefinito. Quindi, aggiungi il prototipo nella parte superiore.
Christian Gingras,

15

In C, "extern" è implicito per i prototipi di funzioni, poiché un prototipo dichiara una funzione che è definita altrove. In altre parole, un prototipo di funzione ha un collegamento esterno per impostazione predefinita; usare 'extern' va bene, ma è ridondante.

(Se è richiesto un collegamento statico, la funzione deve essere dichiarata come "statica" sia nel suo prototipo che nell'intestazione della funzione, e normalmente dovrebbero trovarsi entrambi nello stesso file .c).


8

Un ottimo articolo su cui ho realizzato la externparola chiave, insieme agli esempi: http://www.geeksforgeeks.org/understanding-extern-keyword-in-c/

Anche se non sono d'accordo sul fatto che l'uso delle externdichiarazioni di funzioni sia ridondante. Questa dovrebbe essere un'impostazione del compilatore. Quindi consiglio di usare le externdichiarazioni nella funzione quando è necessario.


3
Ho letto l'articolo geeksforgeeks.org prima di venire qui, ma l'ho trovato scritto male. Oltre alle carenze grammaticali e di sintassi, utilizza molte parole per ribadire lo stesso punto più volte e quindi sfoglia le informazioni critiche. Ad esempio, nell'esempio 4, all'improvviso viene incluso "somefile.h", ma non viene detto altro al di fuori di: "Supponendo che somefile.h abbia la definizione di var". Bene, le informazioni che stiamo "supponendo" sono solo le informazioni che sto cercando. Sfortunatamente, nessuna delle risposte in questa pagina è molto migliore.
Elise van Looij,

6

Se ogni file nel programma viene prima compilato in un file oggetto, i file oggetto vengono collegati insieme, è necessario extern. Indica al compilatore "Questa funzione esiste, ma il codice è altrove. Non fatevi prendere dal panico."


Ehm, ecco come viene normalmente eseguita la traduzione: i file di origine vengono compilati in file oggetto e quindi collegati. Quando non avresti bisogno di esternare in quel caso? Né useresti #include per ottenere funzioni, ma piuttosto prototipi di funzioni. Non capisco di cosa stai parlando.
David Thornley,

Mi sembra di avere questo problema ultimamente di interpretare male le cose. Mi dispiace per quello. Quando ero nuovo in C, # includevo "file.c" per includere semplicemente le funzioni in un file direttamente nell'altro file. Poi ho capito come usare "extern". Pensavo stesse commettendo lo stesso errore.
Chris Lutz,

4

Dovrebbero essere tutte le dichiarazioni di funzioni e variabili nei file di intestazione extern.

Eccezioni a questa regola sono le funzioni incorporate definite nell'intestazione e le variabili che - sebbene definite nell'intestazione - dovranno essere locali all'unità di traduzione (il file sorgente in cui viene inclusa l'intestazione): dovrebbero essere static.

Nei file di origine, externnon deve essere utilizzato per funzioni e variabili definite nel file. Basta aggiungere un prefisso alle definizioni locali statice non fare nulla per le definizioni condivise: per impostazione predefinita saranno simboli esterni.

L'unico motivo per utilizzare externaffatto in un file di origine è dichiarare funzioni e variabili definite in altri file di origine e per le quali non viene fornito alcun file di intestazione.


La dichiarazione di prototipi di funzioni non externè effettivamente necessaria. Ad alcune persone non piace perché sprecherà spazio e le dichiarazioni di funzioni hanno già la tendenza a traboccare i limiti delle linee. Ad altri piace perché in questo modo funzioni e variabili possono essere trattate allo stesso modo.


Puoi spiegare perché "Tutte le dichiarazioni di funzioni e variabili nei file di intestazione devono essere esterne."? Dalle altre risposte mi sembra che siano esterne per impostazione predefinita.
lillq,

@Lane: externè facoltativo per le dichiarazioni di funzione, ma mi piace trattare le variabili e le funzioni allo stesso modo - almeno questa è la cosa più ragionevole che potrei inventare, poiché non ricordo esattamente perché ho iniziato a farlo;)
Christoph

Non è una buona idea includere sempre le variabili globali nel file C in modo che non vengano visualizzate da altri file C casuali che includono l'intestazione. E usare sempre l'esterno su ogni globale tranne il vero sink inizializzato per chiarezza; se è prefissato esternamente, allora è definito altrove.
ste3e,

3

Le funzioni effettivamente definite in altri file di origine devono essere dichiarate solo nelle intestazioni. In questo caso, è necessario utilizzare extern quando si dichiara il prototipo in un'intestazione.

La maggior parte delle volte, le tue funzioni saranno una delle seguenti (più come una best practice):

  • statico (funzioni normali che non sono visibili al di fuori di quel file .c)
  • inline statico (inline da file .c o .h)
  • extern (dichiarazione nelle intestazioni del tipo successivo (vedi sotto))
  • [nessuna parola chiave] (normali funzioni a cui si accede tramite dichiarazioni esterne)

Perché dovresti esternare quando dichiari il prototipo se questo è il valore predefinito?
lillq,

@Lane: potrebbe essere un po 'di parte, ma ogni progetto sano su cui ho lavorato utilizza la seguente convenzione: nelle intestazioni, dichiarare i prototipi solo per funzioni esterne (quindi extern). Nei file .c, i semplici prototipi possono essere utilizzati per ovviare alla necessità di ordini specifici, ma non dovrebbero essere inseriti nelle intestazioni.
Eduard - Gabriel Munteanu,

1

Quando hai quella funzione definita su una diversa dll o lib, in modo che il compilatore passi al linker per trovarla. Il caso tipico è quando si chiamano funzioni dall'API del sistema operativo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.