Effetti della parola chiave extern sulle funzioni C.


172

In C, non ho notato alcun effetto della externparola chiave utilizzata prima della dichiarazione di funzione. All'inizio, ho pensato che quando si definiva extern int f();in un singolo file ti costringesse a implementarlo al di fuori dell'ambito del file. Tuttavia ho scoperto che entrambi:

extern int f();
int f() {return 0;}

e

extern int f() {return 0;}

compilare bene, senza avvisi da gcc. Ho usato gcc -Wall -ansi; non accetterebbe nemmeno //commenti.

Ci sono effetti da utilizzare extern prima delle definizioni delle funzioni ? O è solo una parola chiave opzionale senza effetti collaterali per le funzioni.

In quest'ultimo caso non capisco perché i designer standard abbiano scelto di sporcare la grammatica con parole chiave superflue.

EDIT: Per chiarire, so che c'è un uso externnelle variabili, ma sto solo chiedendo informazioni externsulle funzioni .


Secondo alcune ricerche che ho fatto quando ho tentato di usarlo per alcuni folli scopi di templating, extern non è supportato nella forma prevista dalla maggior parte dei compilatori e quindi non fa davvero nulla.
Ed James,

4
Non è sempre superfluo, vedi la mia risposta. Ogni volta che è necessario condividere qualcosa tra i moduli che NON si desidera in un'intestazione pubblica, è molto utile. Tuttavia, "esternare" ogni singola funzione in un'intestazione pubblica (con compilatori moderni) ha pochissimi o nessun vantaggio, in quanto possono capirlo da soli.
Tim Post

@Ed .. se volatile int foo è un globale in foo.c, e bar.c ne ha bisogno, bar.c deve dichiararlo come esterno. Ha i suoi vantaggi. Oltre a ciò, potrebbe essere necessario condividere alcune funzioni che NON si desidera esporre in un'intestazione pubblica.
Tim Post


2
@Barry Se non altro, l'altra domanda è un duplicato di questo. 2009 vs 2012
Elazar Leibovich,

Risposte:


138

Abbiamo due file, foo.c e bar.c.

Ecco foo.c

#include <stdio.h>

volatile unsigned int stop_now = 0;
extern void bar_function(void);

int main(void)
{
  while (1) {
     bar_function();
     stop_now = 1;
  }
  return 0;
}

Ora, ecco bar.c

#include <stdio.h>

extern volatile unsigned int stop_now;

void bar_function(void)
{
   while (! stop_now) {
      printf("Hello, world!\n");
      sleep(30);
   }
}

Come puoi vedere, non abbiamo un'intestazione condivisa tra foo.c e bar.c, tuttavia bar.c ha bisogno di qualcosa dichiarato in foo.c quando è collegato e foo.c ha bisogno di una funzione da bar.c quando è collegato.

Usando 'extern', stai dicendo al compilatore che qualunque cosa lo segua sarà trovata (non statica) al momento del collegamento; non riservare nulla per esso nel passaggio corrente poiché verrà rilevato in seguito. Funzioni e variabili sono trattate allo stesso modo in questo senso.

È molto utile se devi condividere un po 'di globale tra i moduli e non vuoi metterlo / inizializzarlo in un'intestazione.

Tecnicamente, ogni funzione in un'intestazione pubblica della libreria è "extern", tuttavia etichettarla come tale ha pochissimi o nessun vantaggio, a seconda del compilatore. Molti compilatori possono capirlo da soli. Come vedi, quelle funzioni sono in realtà definite altrove.

Nell'esempio sopra, main () stamperebbe hello world solo una volta, ma continuerebbe a inserire bar_function (). Nota anche che bar_function () non tornerà in questo esempio (dato che è solo un semplice esempio). Immagina solo che stop_now venga modificato quando viene revisionato un segnale (quindi, volatile) se questo non sembra abbastanza pratico.

Gli esterni sono molto utili per cose come i gestori di segnale, un mutex che non si desidera inserire in un'intestazione o in una struttura, ecc. La maggior parte dei compilatori ottimizzerà per garantire che non riservino memoria per oggetti esterni, poiché sanno che lo riserverò nel modulo in cui l'oggetto è definito. Tuttavia, ancora una volta, non ha senso specificarlo con i compilatori moderni durante la prototipazione di funzioni pubbliche.

Spero che aiuti :)


56
Il tuo codice verrà compilato bene senza l'esterno prima della funzione_bar.
Elazar Leibovich,

2
@Tim: Allora non hai avuto il dubbio privilegio di lavorare con il codice con cui lavoro. Può succedere. A volte l'intestazione contiene anche la definizione della funzione statica. È brutto e non necessario il 99,99% delle volte (potrei essere fuori da un ordine o due o grandezza, sopravvalutando la frequenza con cui è necessario). Di solito si verifica quando le persone fraintendono che un'intestazione è necessaria solo quando altri file di origine useranno le informazioni; l'intestazione è (ab) utilizzata per memorizzare le informazioni sulla dichiarazione per un file di origine e nessun altro file dovrebbe includerle. Occasionalmente, si verifica per motivi più contorti.
Jonathan Leffler,

2
@Jonathan Leffler - Davvero dubbioso! Ho ereditato un codice piuttosto impreciso prima, ma posso onestamente dire che non ho mai visto qualcuno mettere una dichiarazione statica in un'intestazione. Sembra che tu abbia un lavoro piuttosto divertente e interessante, però :)
Tim Post

1
Lo svantaggio del "prototipo di funzione non nell'intestazione" è che non si ottiene il controllo automatico indipendente della coerenza tra la definizione della funzione bar.ce la dichiarazione in foo.c. Se la funzione è dichiarata in foo.h ed entrambi i file includono foo.h, l'intestazione impone la coerenza tra i due file di origine. Senza di essa, se la definizione di bar_functionin bar.ccambia ma la dichiarazione in foo.cnon viene modificata, allora le cose vanno male in fase di esecuzione; il compilatore non è in grado di individuare il problema. Con un'intestazione correttamente utilizzata, il compilatore individua il problema.
Jonathan Leffler,

1
extern sulle dichiarazioni di funzioni è superfluo come "int" in "unsigned int". È buona norma usare "extern" quando il prototipo NON è una dichiarazione in avanti ... Ma dovrebbe davvero vivere in un'intestazione senza "extern" a meno che l'attività non sia un caso limite. stackoverflow.com/questions/10137037/...

82

Per quanto ricordo lo standard, tutte le dichiarazioni di funzioni sono considerate "esterne" per impostazione predefinita, quindi non è necessario specificarle esplicitamente.

Ciò non rende questa parola chiave inutile poiché può essere utilizzata anche con le variabili (e in tal caso - è l'unica soluzione per risolvere i problemi di collegamento). Ma con le funzioni: sì, è facoltativo.


21
Quindi come designer standard non consentirò di usare extern con le funzioni, poiché aggiunge solo rumore alla grammatica.
Elazar Leibovich,

3
La compatibilità con le versioni precedenti può essere una seccatura.
MathuSum Mut

1
@ElazarLeibovich In realtà, in questo caso particolare, non consentirlo è ciò che aggiungerebbe rumore alla grammatica.
Corse di leggerezza in orbita,

1
Come limitare una parola chiave aggiunge rumore è al di là di me, ma immagino sia una questione di gusti.
Elazar Leibovich,

È utile consentire l'uso di "extern" per le funzioni, poiché indica ad altri programmatori che la funzione è definita in un altro file, non nel file corrente e non è dichiarata in una delle intestazioni incluse.
DimP

23

È necessario distinguere tra due concetti separati: definizione della funzione e dichiarazione dei simboli. "extern" è un modificatore di collegamento, un suggerimento per il compilatore su dove viene definito il simbolo a cui si fa riferimento in seguito (il suggerimento è "non qui").

Se scrivo

extern int i;

nell'ambito del file (al di fuori di un blocco funzione) in un file C, quindi stai dicendo "la variabile può essere definita altrove".

extern int f() {return 0;}

è sia una dichiarazione della funzione f che una definizione della funzione f. La definizione in questo caso sovrascrive l'esterno.

extern int f();
int f() {return 0;}

è prima una dichiarazione, seguita dalla definizione.

L'uso di externè errato se si desidera dichiarare e contemporaneamente definire una variabile dell'ambito del file. Per esempio,

extern int i = 4;

fornirà un errore o un avviso, a seconda del compilatore.

L'uso di externè utile se si desidera evitare esplicitamente la definizione di una variabile.

Lasciatemi spiegare:

Supponiamo che il file ac contenga:

#include "a.h"

int i = 2;

int f() { i++; return i;}

Il file ah include:

extern int i;
int f(void);

e il file bc contiene:

#include <stdio.h>
#include "a.h"

int main(void){
    printf("%d\n", f());
    return 0;
}

L'esterno nell'intestazione è utile perché dice al compilatore durante la fase di collegamento "questa è una dichiarazione e non una definizione". Se rimuovo la linea in ac che definisce i, alloca spazio per essa e gli assegna un valore, il programma non dovrebbe compilare con un riferimento indefinito. Questo dice allo sviluppatore che ha fatto riferimento a una variabile, ma non l'ha ancora definita. Se, d'altra parte, ometto la parola chiave "extern" e rimuovo la int i = 2riga, il programma si compila ancora - sarò definito con un valore predefinito di 0.

Le variabili dell'ambito del file vengono definite in modo implicito con un valore predefinito pari a 0 o NULL se non si assegna loro esplicitamente un valore, diversamente dalle variabili dell'ambito del blocco dichiarate all'inizio di una funzione. La parola chiave extern evita questa definizione implicita e quindi aiuta a evitare errori.

Per le funzioni, nelle dichiarazioni di funzioni, la parola chiave è effettivamente ridondante. Le dichiarazioni di funzione non hanno una definizione implicita.


Intendevi rimuovere la int i = 2riga nel terzo paragrafo? Ed è corretto dichiarare, vedendo int i;, il compilatore allocerà la memoria per quella variabile, ma vedendo extern int i;, il compilatore NON allocerà la memoria ma cercherà la variabile altrove?
Frozen Flame,

In realtà se si omette la parola chiave "extern" il programma non verrà compilato a causa della ridefinizione di i in ac e bc (a causa di ah).
Nixt,

15

La externparola chiave assume forme diverse a seconda dell'ambiente. Se è disponibile una dichiarazione, la externparola chiave prende il collegamento come precedentemente specificato nell'unità di traduzione. In assenza di tale dichiarazione, externspecifica il collegamento esterno.

static int g();
extern int g(); /* g has internal linkage */

extern int j(); /* j has tentative external linkage */

extern int h();
static int h(); /* error */

Ecco i paragrafi pertinenti del progetto C99 (n1256):

6.2.2 Collegamenti di identificatori

[...]

4 Per un identificatore dichiarato con l'identificatore della classe di archiviazione esterno in un ambito in cui è visibile una precedente dichiarazione di tale identificatore, 23) se la dichiarazione precedente specifica un collegamento interno o esterno, il collegamento dell'identificatore nella dichiarazione successiva è lo stesso come il collegamento specificato nella dichiarazione precedente. Se non è visibile alcuna dichiarazione precedente o se la dichiarazione precedente non specifica alcun collegamento, l'identificatore ha un collegamento esterno.

5 Se la dichiarazione di un identificatore per una funzione non ha un identificatore della classe di archiviazione, il suo collegamento viene determinato esattamente come se fosse dichiarato con l'identificatore della classe di archiviazione esterno. Se la dichiarazione di un identificatore per un oggetto ha un ambito file e nessun identificatore della classe di archiviazione, il suo collegamento è esterno.


È lo standard o mi stai semplicemente dicendo il comportamento di un tipico compilatore? In caso di standard, sarò felice per un link allo standard. Ma grazie!
Elazar Leibovich,

Questo è il comportamento standard. La bozza C99 è disponibile qui: < open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf >. Lo standard attuale non è gratuito (la bozza è abbastanza buona per la maggior parte degli scopi).
Dirkgently

1
L'ho appena testato in gcc ed è entrambi "extern int h (); static int h () {return 0;}" e "int h (); static int h () {return 0;}" sono accettati con lo stesso avvertimento. È solo C99 e non ANSI? Puoi riferirmi alla sezione esatta nella bozza, dal momento che questo non sembra essere vero per gcc.
Elazar Leibovich,

Controlla di nuovo. Ho provato lo stesso con gcc 4.0.1 e ricevo un errore proprio dove dovrebbe essere. Prova anche il compilatore online o codepad.org di comeau se non hai accesso ad altri compilatori. Leggi lo standard.
Dirkgently

2
@dirkgently, La mia vera domanda è che c'è qualche effetto sull'uso di exetrn con la dichiarazione di funzione, e se non c'è nessuno perché è possibile aggiungere extern a una dichiarazione di funzione. E la risposta è no, non c'è alcun effetto, e una volta c'era un effetto con compilatori non così standard.
Elazar Leibovich,

11

Le funzioni incorporate hanno regole speciali su ciò che externsignifica. (Notare che le funzioni inline sono un'estensione C99 o GNU; non erano in C. originale

Per le funzioni non in linea, externnon è necessario in quanto è attivo per impostazione predefinita.

Si noti che le regole per C ++ sono diverse. Ad esempio, extern "C"è necessario sulla dichiarazione C ++ delle funzioni C che si intende chiamare da C ++ e esistono regole diverse inline.


Questa è l'unica risposta qui che entrambi sono corretti e rispondono effettivamente alla domanda.
robinjam,

4

IOW, extern è ridondante e non fa nulla.

Ecco perché, 10 anni dopo:

Vedi commit ad6dad0 , commit b199d71 , commit 5545442 (29 aprile 2019) di Denton Liu ( Denton-L) .
(Unito da Junio ​​C Hamano - gitster- in commit 4aeeef3 , 13 maggio 2019)

*.[ch]: rimuove externdalle dichiarazioni di funzioni usandospatch

C'è stata una spinta per rimuovere externdalle dichiarazioni di funzione.

Rimuovere alcune istanze di " extern" per le dichiarazioni di funzione catturate da Coccinelle.
Nota che Coccinelle ha qualche difficoltà con l'elaborazione delle funzioni con __attribute__o varargs, quindi alcune externdichiarazioni vengono lasciate indietro per essere trattate in una patch futura.

Questa era la patch Coccinelle utilizzata:

  @@
    type T;
    identifier f;
    @@
    - extern
    T f(...);

ed è stato eseguito con:

  $ git ls-files \*.{c,h} |
    grep -v ^compat/ |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place

Questo non è sempre semplice:

Vedi commit 7027f50 (04 set 2019) di Denton Liu ( Denton-L) .
(Unito da Denton Liu - Denton-L- in commit 7027f50 , 05 set 2019)

compat/*.[ch]: rimuove externdalle dichiarazioni di funzioni usando spatch

In 5545442 ( *.[ch]: rimuovi externdalle dichiarazioni di funzione usando spatch, 29-04-2019, Git v2.22.0-rc0), abbiamo rimosso gli esterni dalle dichiarazioni di funzione usandospatch ma abbiamo intenzionalmente escluso i file in compat/quanto alcuni vengono copiati direttamente da un upstream e dovremmo evitare sfornandoli in modo che la fusione manuale degli aggiornamenti futuri sarà più semplice.

Nell'ultimo commit, abbiamo determinato i file presi da un upstream in modo da poterli escludere ed eseguire spatch il resto.

Questa era la patch Coccinelle utilizzata:

@@
type T;
identifier f;
@@
- extern
  T f(...);

ed è stato eseguito con:

$ git ls-files compat/\*\*.{c,h} |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Coccinelle ha qualche problema a che fare con __attribute__varargs e quindi abbiamo eseguito quanto segue per garantire che non rimanessero cambiamenti rimanenti:

$ git ls-files compat/\*\*.{c,h} |
    xargs sed -i'' -e 's/^\(\s*\)extern \([^(]*([^*]\)/\1\2/'
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Nota che con Git 2.24 (Q4 2019), ogni spurio externviene eliminato.

Vedi commit 65904b8 (30 set 2019) di Emily Shaffer ( nasamuffin) .
Aiutato da: Jeff King ( peff) .
Vedi commit 8464f94 (21 set 2019) di Denton Liu ( Denton-L) .
Aiutato da: Jeff King ( peff) .
(Unito da Junio ​​C Hamano - gitster- in commit 59b19bc , 07 ott 2019)

promisor-remote.h: far cadere extern dalla dichiarazione di funzione

Durante la creazione di questo file, ogni volta che veniva introdotta una nuova dichiarazione di funzione, includeva un extern.
Tuttavia, a partire da 5545442 ( *.[ch]: rimuovere externdalle dichiarazioni di funzione utilizzandospatch , 29-04-2019, Git v2.22.0-rc0), abbiamo attivamente cercato di impedire l'utilizzo di externs nelle dichiarazioni di funzione perché non sono necessarie.

Rimuovi questi spuri extern.


3

La externparola chiave informa il compilatore che la funzione o la variabile ha un collegamento esterno - in altre parole, è visibile da file diversi da quello in cui è definita. In questo senso ha il significato opposto alla staticparola chiave. È un po 'strano mettere external momento della definizione, poiché nessun altro file avrebbe visibilità della definizione (o si tradurrebbe in più definizioni). Normalmente si inserisce externuna dichiarazione ad un certo punto con visibilità esterna (come un file di intestazione) e si inserisce la definizione altrove.


2

dichiarare una funzione esternamente significa che la sua definizione verrà risolta al momento del collegamento, non durante la compilazione.

A differenza delle normali funzioni, che non sono dichiarate esternamente, può essere definita in uno qualsiasi dei file di origine (ma non in più file di origine, altrimenti si otterrà un errore del linker che dice che hai dato più definizioni della funzione) incluso quello in che viene dichiarato esternamente. Quindi, in ogni caso, il linker risolve la definizione della funzione nello stesso file.

Non penso che fare questo sarebbe molto utile, tuttavia fare questo tipo di esperimenti fornisce una migliore comprensione di come funziona il compilatore e il linker del linguaggio.


2
IOW, extern è ridondante e non fa nulla. Sarebbe molto più chiaro se la mettessi così.
Elazar Leibovich,

@ElazarLeibovich Ho appena riscontrato un caso simile nel nostro codice e ho avuto la stessa conclusione. Tutte quelle risposte qui possono essere riassunte in una sola riga. Non ha alcun effetto pratico ma potrebbe essere utile per la leggibilità. Piacere di vederti online e non solo nei Meetup :)
Aviv,

1

Il motivo per cui non ha alcun effetto è perché al momento del collegamento il linker tenta di risolvere la definizione esterna (nel tuo caso extern int f()). Non importa se lo trova nello stesso file o in un altro file, purché venga trovato.

Spero che questo risponda alla tua domanda.


1
Allora perché consentire di aggiungere externa qualsiasi funzione?
Elazar Leibovich,

2
Si prega di astenersi dal inserire spam non correlato nei propri post. Grazie!
Mac,
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.