Auswirkungen des Schlüsselworts extern auf C-Funktionen

171

In C habe ich keine Auswirkung des externvor der Funktionsdeklaration verwendeten Schlüsselworts festgestellt. Zuerst dachte ich, dass Sie beim Definieren extern int f();in einer einzelnen Datei gezwungen sind, diese außerhalb des Dateibereichs zu implementieren. Ich fand jedoch heraus, dass beide:

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

und

extern int f() {return 0;}

Kompilieren Sie ganz gut, ohne Warnungen von gcc. Ich habe benutzt gcc -Wall -ansi; es würde nicht einmal //Kommentare akzeptieren .

Gibt es irgendwelche Auswirkungen für die Verwendung extern vor Funktionsdefinitionen ? Oder ist es nur ein optionales Schlüsselwort ohne Nebenwirkungen für Funktionen?

Im letzteren Fall verstehe ich nicht, warum die Standarddesigner beschlossen haben, die Grammatik mit überflüssigen Schlüsselwörtern zu verunreinigen.

EDIT: Um zu klären, ich weiß , dass es Verwendung für externVariablen, aber ich frage nur externin Funktionen .

Elazar Leibovich
quelle
Nach einigen Recherchen, die ich durchgeführt habe, als ich versucht habe, dies für verrückte Vorlagenzwecke zu verwenden, wird extern nicht in der Form unterstützt, wie es von den meisten Compilern beabsichtigt ist, und macht daher eigentlich nichts.
Ed James
4
Es ist nicht immer überflüssig, siehe meine Antwort. Jedes Mal, wenn Sie etwas zwischen Modulen teilen müssen, das Sie NICHT in einem öffentlichen Header haben möchten, ist dies sehr nützlich. Das "Externieren" jeder einzelnen Funktion in einem öffentlichen Header (mit modernen Compilern) hat jedoch nur sehr geringe bis gar keine Vorteile, da sie dies selbst herausfinden können.
Tim Post
@Ed .. Wenn volatile int foo in foo.c global ist und bar.c es benötigt, muss bar.c es als extern deklarieren. Es hat seine Vorteile. Darüber hinaus müssen Sie möglicherweise eine Funktion freigeben, die NICHT in einem öffentlichen Header verfügbar gemacht werden soll.
Tim Post
Siehe auch: stackoverflow.com/questions/496448/…
Steve Melnikoff
2
@Barry Wenn überhaupt, ist die andere Frage ein Duplikat dieser. 2009 gegen 2012
Elazar Leibovich

Antworten:

138

Wir haben zwei Dateien, foo.c und bar.c.

Hier ist 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;
}

Hier ist bar.c

#include <stdio.h>

extern volatile unsigned int stop_now;

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

Wie Sie sehen können, haben wir keinen gemeinsamen Header zwischen foo.c und bar.c, jedoch benötigt bar.c etwas, das in foo.c deklariert ist, wenn es verknüpft ist, und foo.c benötigt eine Funktion von bar.c, wenn es verknüpft ist.

Wenn Sie 'extern' verwenden, teilen Sie dem Compiler mit, dass alles, was folgt, zum Zeitpunkt der Verknüpfung gefunden wird (nicht statisch). Reservieren Sie im aktuellen Pass nichts dafür, da es später angetroffen wird. Funktionen und Variablen werden in dieser Hinsicht gleich behandelt.

Dies ist sehr nützlich, wenn Sie einige globale Elemente zwischen Modulen teilen müssen und diese nicht in einen Header einfügen / initialisieren möchten.

Technisch gesehen ist jede Funktion in einem öffentlichen Bibliotheksheader "extern", die Kennzeichnung als solche hat jedoch je nach Compiler nur sehr geringe oder gar keine Vorteile. Die meisten Compiler können das selbst herausfinden. Wie Sie sehen, sind diese Funktionen tatsächlich woanders definiert.

Im obigen Beispiel würde main () hallo world nur einmal drucken, aber weiterhin bar_function () eingeben. Beachten Sie auch, dass bar_function () in diesem Beispiel nicht zurückgegeben wird (da es sich nur um ein einfaches Beispiel handelt). Stellen Sie sich vor, stop_now wird geändert, wenn ein Signal bedient wird (daher flüchtig), wenn dies nicht praktisch genug erscheint.

Externe sind sehr nützlich für Dinge wie Signalhandler, einen Mutex, den Sie nicht in einen Header oder eine Struktur einfügen möchten usw. Die meisten Compiler optimieren, um sicherzustellen, dass sie keinen Speicher für externe Objekte reservieren, da sie diese kennen Ich werde es in dem Modul reservieren, in dem das Objekt definiert ist. Es macht jedoch wenig Sinn, es mit modernen Compilern zu spezifizieren, wenn öffentliche Funktionen prototypisiert werden.

Hoffentlich hilft das :)

Tim Post
quelle
56
Ihr Code wird ohne das externe vor der bar_function einwandfrei kompiliert.
Elazar Leibovich
2
@ Tim: Dann hatten Sie nicht das zweifelhafte Privileg, mit dem Code zu arbeiten, mit dem ich arbeite. Es kann vorkommen. Manchmal enthält der Header auch die statische Funktionsdefinition. Es ist hässlich und in 99,99% der Fälle unnötig (ich könnte um ein oder zwei Größenordnungen oder mehr davon abweichen und übertreiben, wie oft es notwendig ist). Es tritt normalerweise auf, wenn Leute falsch verstehen, dass ein Header nur benötigt wird, wenn andere Quelldateien die Informationen verwenden. Der Header wird (ab) zum Speichern von Deklarationsinformationen für eine Quelldatei verwendet, und es wird nicht erwartet, dass eine andere Datei diese enthält. Gelegentlich tritt es aus verzerrten Gründen auf.
Jonathan Leffler
2
@ Jonathan Leffler - In der Tat zweifelhaft! Ich habe schon einmal einen ziemlich skizzenhaften Code geerbt, aber ich kann ehrlich sagen, dass ich noch nie jemanden gesehen habe, der eine statische Deklaration in einen Header eingefügt hat. Es hört sich aber so an, als hättest du einen ziemlich lustigen und interessanten Job :)
Tim Post
1
Der Nachteil des 'Funktionsprototyps nicht im Header' besteht darin, dass Sie nicht die automatische unabhängige Überprüfung der Konsistenz zwischen der Funktionsdefinition in bar.cund der Deklaration in erhalten foo.c. Wenn die Funktion in deklariert ist foo.h und beide Dateien enthalten foo.h, erzwingt der Header die Konsistenz zwischen den beiden Quelldateien. Wenn ohne sie die Definition von bar_functionin bar.cgeändert wird, aber die Deklaration in foo.cnicht geändert wird, laufen die Dinge zur Laufzeit schief. Der Compiler kann das Problem nicht erkennen. Wenn ein Header ordnungsgemäß verwendet wird, erkennt der Compiler das Problem.
Jonathan Leffler
1
extern on functions-Deklarationen sind überflüssig wie 'int' in 'unsigned int'. Es ist empfehlenswert, 'extern' zu verwenden, wenn der Prototyp KEINE Vorwärtsdeklaration ist ... Aber es sollte wirklich in einem Header ohne 'extern' leben, es sei denn, die Aufgabe ist ein Randfall. stackoverflow.com/questions/10137037/…
82

Soweit ich mich an den Standard erinnere, werden alle Funktionsdeklarationen standardmäßig als "extern" betrachtet, sodass keine explizite Angabe erforderlich ist.

Das macht dieses Schlüsselwort nicht unbrauchbar, da es auch mit Variablen verwendet werden kann (und in diesem Fall - es ist die einzige Lösung, um Verknüpfungsprobleme zu lösen). Aber mit den Funktionen - ja, es ist optional.


quelle
21
Dann werde ich als Standarddesigner die Verwendung von extern mit Funktionen nicht zulassen , da dies nur der Grammatik Rauschen hinzufügt.
Elazar Leibovich
3
Abwärtskompatibilität kann schmerzhaft sein.
MathuSum Mut
1
@ElazarLeibovich in diesem speziellen Fall Eigentlich, Verbieten es , was Lärm die Grammatik möchte hinzufügen , ist.
Leichtigkeitsrennen im Orbit
1
Wie die Begrenzung eines Keywords zu Rauschen führt, ist mir ein Rätsel , aber ich denke, es ist Geschmackssache.
Elazar Leibovich
Es ist jedoch nützlich, die Verwendung von "extern" für Funktionen zuzulassen, da dies anderen Programmierern anzeigt, dass die Funktion in einer anderen Datei definiert ist, nicht in der aktuellen Datei und auch nicht in einem der enthaltenen Header deklariert ist.
DimP
23

Sie müssen zwischen zwei getrennten Konzepten unterscheiden: Funktionsdefinition und Symboldeklaration. "extern" ist ein Verknüpfungsmodifikator, ein Hinweis an den Compiler, wo das Symbol definiert ist, auf das später Bezug genommen wird (der Hinweis lautet "nicht hier").

Wenn ich schreibe

extern int i;

Im Dateibereich (außerhalb eines Funktionsblocks) in einer C-Datei sagen Sie dann "Die Variable kann an anderer Stelle definiert werden".

extern int f() {return 0;}

ist sowohl eine Deklaration der Funktion f als auch eine Definition der Funktion f. Die Definition überschreibt in diesem Fall das Externe.

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

ist zunächst eine Erklärung, gefolgt von der Definition.

Die Verwendung von externist falsch, wenn Sie eine Dateibereichsvariable deklarieren und gleichzeitig definieren möchten. Beispielsweise,

extern int i = 4;

gibt je nach Compiler einen Fehler oder eine Warnung aus.

Die Verwendung von externist nützlich, wenn Sie die Definition einer Variablen explizit vermeiden möchten.

Lassen Sie mich erklären:

Angenommen, die Datei ac enthält:

#include "a.h"

int i = 2;

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

Die Datei ah enthält:

extern int i;
int f(void);

und die Datei bc enthält:

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

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

Das externe Element im Header ist nützlich, da es dem Compiler während der Verbindungsphase mitteilt, dass dies eine Deklaration und keine Definition ist. Wenn ich die Zeile in ac entferne, die i definiert, ihm Speicherplatz zuweist und ihm einen Wert zuweist, sollte das Programm nicht mit einer undefinierten Referenz kompiliert werden können. Dies teilt dem Entwickler mit, dass er auf eine Variable verwiesen, diese aber noch nicht definiert hat. Wenn ich andererseits das Schlüsselwort "extern" weglasse und die int i = 2Zeile entferne , wird das Programm immer noch kompiliert - ich werde mit einem Standardwert von 0 definiert.

Dateibereichsvariablen werden implizit mit einem Standardwert von 0 oder NULL definiert, wenn Sie ihnen keinen expliziten Wert zuweisen - im Gegensatz zu Blockbereichsvariablen, die Sie oben in einer Funktion deklarieren. Das Schlüsselwort extern vermeidet diese implizite Definition und hilft so, Fehler zu vermeiden.

Für Funktionen in Funktionsdeklarationen ist das Schlüsselwort tatsächlich redundant. Funktionsdeklarationen haben keine implizite Definition.

Dave Neary
quelle
int i = 2Wollten Sie die Zeile im 3. Absatz entfernen ? Und ist es richtig zu sagen int i;, dass der Compiler Speicher für diese Variable zuweist, aber dass extern int i;der Compiler NICHT Speicher zuweist, sondern woanders nach der Variablen sucht?
Frozen Flame
Wenn Sie das Schlüsselwort "extern" weglassen, wird das Programm aufgrund der Neudefinition von i in ac und bc (aufgrund von ah) nicht kompiliert.
Nixt
15

Das externSchlüsselwort nimmt je nach Umgebung unterschiedliche Formen an. Wenn eine Deklaration verfügbar ist, nimmt das externSchlüsselwort die Verknüpfung wie zuvor in der Übersetzungseinheit angegeben an. Wenn keine solche Erklärung externvorliegt, wird eine externe Verknüpfung angegeben.

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 */

Hier sind die relevanten Absätze aus dem C99-Entwurf (n1256):

6.2.2 Verknüpfungen von Kennungen

[...]

4 Für einen Bezeichner, der mit dem Speicherklassenspezifizierer extern in einem Bereich deklariert wurde, in dem eine vorherige Deklaration dieses Bezeichners sichtbar ist, 23) wenn die vorherige Deklaration eine interne oder externe Verknüpfung angibt, ist die Verknüpfung des Bezeichners bei der späteren Deklaration dieselbe als die in der vorherigen Erklärung angegebene Verknüpfung. Wenn keine vorherige Deklaration sichtbar ist oder wenn die vorherige Deklaration keine Verknüpfung angibt, verfügt der Bezeichner über eine externe Verknüpfung.

5 Wenn die Deklaration eines Bezeichners für eine Funktion keinen Speicherklassenspezifizierer hat, wird ihre Verknüpfung genau so bestimmt, als ob sie mit dem Speicherklassenspezifizierer extern deklariert worden wäre. Wenn die Deklaration eines Bezeichners für ein Objekt einen Dateibereich und keinen Speicherklassenspezifizierer hat, ist die Verknüpfung extern.

dirkgently
quelle
Ist es der Standard oder erzählen Sie mir nur das Verhalten eines typischen Compilers? Im Falle des Standards freue ich mich über einen Link zum Standard. Aber danke!
Elazar Leibovich
Dies ist das Standardverhalten. Der C99-Entwurf ist hier verfügbar: < open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf >. Der eigentliche Standard ist jedoch nicht kostenlos (der Entwurf ist für die meisten Zwecke gut genug).
Dirkgently
1
Ich habe es gerade in gcc getestet und es wird sowohl "extern int h (); static int h () {return 0;}" als auch "int h (); static int h () {return 0;}" mit demselben akzeptiert Warnung. Ist es nur C99 und nicht ANSI? Können Sie mich auf den genauen Abschnitt im Entwurf verweisen, da dies für gcc nicht zu gelten scheint?
Elazar Leibovich
Erneut überprüfen. Ich habe das gleiche mit gcc 4.0.1 versucht und bekomme eine Fehlermeldung genau dort, wo es sein sollte. Probieren Sie auch den Online-Compiler von comeau oder codepad.org aus, wenn Sie keinen Zugriff auf andere Compiler haben. Lesen Sie den Standard.
Dirkgently
2
@dirkgently, meine eigentliche Frage ist, ob es einen Effekt für die Verwendung von exetrn mit Funktionsdeklaration gibt, und wenn es keinen gibt, warum ist es möglich, einer Funktionsdeklaration extern hinzuzufügen. Und die Antwort ist nein, es gibt keinen Effekt, und es gab einmal einen Effekt mit nicht so standardmäßigen Compilern.
Elazar Leibovich
11

Inline-Funktionen haben spezielle Regeln, was externbedeutet. (Beachten Sie, dass Inline-Funktionen eine C99- oder GNU-Erweiterung sind; sie waren nicht im Original C.

Für Nicht-Inline-Funktionen externwird dies nicht benötigt, da es standardmäßig aktiviert ist.

Beachten Sie, dass die Regeln für C ++ unterschiedlich sind. Wird beispielsweise extern "C"für die C ++ - Deklaration von C-Funktionen benötigt, die Sie von C ++ aus aufrufen möchten, und es gibt unterschiedliche Regeln für inline.

user9876
quelle
Dies ist die einzige Antwort hier, bei der beide richtig sind und die Frage tatsächlich beantworten.
Robinjam
4

IOW, extern ist redundant und tut nichts.

Deshalb 10 Jahre später:

Siehe Commit ad6dad0 , Commit b199d71 , Commit 5545442 (29. April 2019) von Denton Liu ( Denton-L) .
(Zusammengeführt von Junio ​​C Hamano - gitster- in Commit 4aeeef3 , 13. Mai 2019)

*.[ch]: externaus Funktionsdeklarationen entfernen mitspatch

Es wurde versucht, externaus Funktionsdeklarationen zu entfernen .

Entfernen Sie einige Instanzen von " extern" für Funktionsdeklarationen, die von Coccinelle abgefangen werden.
Beachten Sie, dass Coccinelle einige Schwierigkeiten bei der Verarbeitung von Funktionen mit __attribute__oder varargs hat, sodass einige externDeklarationen zurückbleiben, um in einem zukünftigen Patch behandelt zu werden.

Dies war das verwendete Coccinelle-Pflaster:

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

und es wurde ausgeführt mit:

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

Dies ist jedoch nicht immer einfach:

Siehe Commit 7027f50 (04. September 2019) von Denton Liu ( Denton-L) .
(Zusammengeführt von Denton Liu - Denton-L- in Commit 7027f50 , 05. September 2019)

compat/*.[ch]: externMit Spatch aus Funktionsdeklarationen entfernen

In 5545442 ( *.[ch]: Entfernen externaus Funktionsdeklarationen mit Spatch, 29.04.2019, Git v2.22.0-rc0) haben wir Externe aus Funktionsdeklarationen mit entfernt, spatchaber absichtlich Dateien unter ausgeschlossen, compat/da einige direkt von einem Upstream kopiert werden und wir sollten dies vermeiden Sie können sie einfacher zusammenführen, um zukünftige Updates manuell zusammenzuführen.

Beim letzten Commit haben wir die Dateien ermittelt, die aus einem Upstream stammen, damit wir sie ausschließen und spatchfür den Rest ausführen können .

Dies war das verwendete Coccinelle-Pflaster:

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

und es wurde ausgeführt mit:

$ 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 hat einige Probleme mit __attribute__und Varargs, daher haben wir Folgendes ausgeführt, um sicherzustellen, dass keine verbleibenden Änderungen zurückgelassen wurden:

$ 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/

Beachten Sie, dass mit Git 2.24 (Q4 2019) alle falschen Daten externgelöscht werden.

Siehe Commit 65904b8 (30. September 2019) von Emily Shaffer ( nasamuffin) .
Mit freundlicher Unterstützung von Jeff King ( peff) .
Siehe Commit 8464f94 (21. September 2019) von Denton Liu ( Denton-L) .
Mit freundlicher Unterstützung von Jeff King ( peff) .
(Zusammengeführt von Junio ​​C Hamano - gitster- in Commit 59b19bc , 07. Oktober 2019)

promisor-remote.h: Drop externaus Funktionsdeklaration

Während der Erstellung dieser Datei enthielt jedes Mal, wenn eine neue Funktionsdeklaration eingeführt wurde, eine extern.
Ab 5545442 ( *.[ch]: Entfernen externaus Funktionsdeklarationen mit spatch, 2019-04-29, Git v2.22.0-rc0) haben wir jedoch aktiv versucht, zu verhindern, dass externe Elemente in Funktionsdeklarationen verwendet werden, da sie nicht erforderlich sind.

Entfernen Sie diese falschen externs.

VonC
quelle
3

Das externSchlüsselwort informiert den Compiler darüber, dass die Funktion oder Variable über eine externe Verknüpfung verfügt - mit anderen Worten, dass sie aus anderen Dateien als der, in der sie definiert ist, sichtbar ist. In diesem Sinne hat es die entgegengesetzte Bedeutung zum staticSchlüsselwort. Es ist ein bisschen komisch zu sagenextern zum Zeitpunkt der Definition zu setzen, da keine anderen Dateien die Definition sichtbar machen würden (oder dies würde zu mehreren Definitionen führen). Normalerweise geben Sie externirgendwann eine Deklaration mit externer Sichtbarkeit ein (z. B. eine Header-Datei) und platzieren die Definition an einer anderen Stelle.

1800 INFORMATIONEN
quelle
2

Das Deklarieren einer Funktion extern bedeutet, dass ihre Definition zum Zeitpunkt der Verknüpfung und nicht während der Kompilierung aufgelöst wird.

Im Gegensatz zu regulären Funktionen, die nicht als extern deklariert sind, kann sie in jeder der Quelldateien definiert werden (jedoch nicht in mehreren Quelldateien, da sonst ein Linkerfehler angezeigt wird, der besagt, dass Sie mehrere Definitionen der Funktion angegeben haben), einschließlich der in Dies wird als extern deklariert. In diesem Fall löst der Linker die Funktionsdefinition in derselben Datei auf.

Ich denke nicht, dass dies sehr nützlich wäre, aber solche Experimente geben einen besseren Einblick in die Funktionsweise des Compilers und Linkers der Sprache.

Rampal Chaudhary
quelle
2
IOW, extern ist redundant und tut nichts. Es wäre viel klarer, wenn Sie es so ausdrücken würden.
Elazar Leibovich
@ElazarLeibovich Ich bin gerade auf einen ähnlichen Fall in unserer Codebasis gestoßen und hatte die gleiche Schlussfolgerung. Alle diese Antworten hier können in Ihrem Einzeiler zusammengefasst werden. Es hat keine praktische Wirkung, kann aber für die Lesbarkeit hilfreich sein. Schön dich online zu sehen und nicht nur in Meetups :)
Aviv
1

Der Grund dafür ist, dass der Linker zum Zeitpunkt der Verknüpfung versucht, die externe Definition aufzulösen (in Ihrem Fall extern int f()). Es spielt keine Rolle, ob es in derselben oder einer anderen Datei gefunden wird, solange es gefunden wird.

Hoffe das beantwortet deine Frage.

Umer
quelle
1
Warum sollte man externdann überhaupt eine Funktion hinzufügen ?
Elazar Leibovich
2
Bitte platzieren Sie keinen Spam in Ihren Posts. Vielen Dank!
Mac