Warum sind einige Funktionen extern deklariert und die Header-Datei nicht im Source-Quellcode enthalten?

8

Ich wollte den Quellcode einer realen Anwendung sehen, um gute Programmierpraktiken usw. zu verstehen. Also wählte ich Git und lud die Quelle für Version 1.8.4 herunter.

Nachdem ich zufällig verschiedene Dateien durchgeblättert hatte, fiel mir in diesen beiden Dateien etwas auf: strbuf.h strbuf.c

Diese beiden Dateien definieren anscheinend eine API mit dieser Dokumentation .

Ich habe zwei Fragen:

  1. Warum die Funktionsdeklarationen in Zeile 16, 17, 18, 19 & amp; globale Variable in Zeile 6 in 'strbuf.h' extern erklärt?

  2. Warum ist "strbuf.h" nicht in strbuf .c enthalten?

Ich habe als Anfänger gelernt, dass Sie Funktionsdefinitionen in eine .c-Datei schreiben, während die Funktionsdeklarationen, Makros, Inlines etc. in eine .h-Datei geschrieben werden, die dann in jeder .c-Datei enthalten ist um diese Funktionen zu benutzen etc.

Kann jemand bitte das erklären?

    
rsjethani 11.08.2013, 11:20
quelle

1 Antwort

23

strbuf.c enthält cache.h und cache.h enthält strbuf.h , also ist Ihre Prämisse für Frage 2 (das strbuf.c enthält nicht strbuf.h ) falsch: es enthält es, nur nicht direkt.

extern angewendet auf Funktionen

Das Schlüsselwort extern wird niemals für Funktionsdeklarationen benötigt, hat aber eine Auswirkung: Es gibt an, dass der Bezeichner der Funktion (dh der Name der Funktion) die gleiche Verknüpfung hat wie jede zuvor sichtbare Deklaration oder wenn nein Eine solche Deklaration ist sichtbar, dass der Bezeichner eine externe Verknüpfung hat. Diese ziemlich verwirrende Formulierung bedeutet wirklich, dass:

%Vor%

Die zweite Deklaration von foo deklariert auch static und gibt ihr eine interne Verknüpfung. Wenn Sie schreiben:

%Vor%

Sie haben es zuerst als interne Verknüpfung und dann als zweite externe Verknüpfung deklariert, und in Versionen vor 1999 von C 1 , die undefiniertes Verhalten erzeugt. In einer Hinsicht fügt das Schlüsselwort extern etwas Sicherheit hinzu (zum Preis der Verwirrung), da es bei Bedarf static bedeuten kann. Du könntest aber immer wieder static schreiben und extern ist kein Allheilmittel:

%Vor%

Diese dritte Form ist immer noch fehlerhaft. Die erste extern -Deklaration hat keine vorherige sichtbare Deklaration, also hat foo eine externe Verknüpfung, und dann gibt die zweite static -Deklaration foo interne Verknüpfung, was zu undefiniertem Verhalten führt.

Kurz gesagt, extern ist für Funktionsdeklarationen nicht erforderlich. Manche Leute bevorzugen es einfach aus Stilgründen.

(Hinweis: Ich verlasse extern inline in C99, was irgendwie seltsam ist, und Implementierungen variieren. Siehe Ссылка für weitere Details.)

extern angewendet auf Variablendeklarationen

Das Schlüsselwort extern für eine Variablendeklaration hat mehrere verschiedene Auswirkungen. Wie bei den Funktionsdeklarationen wirkt sich dies erstens auf die Verknüpfung des Bezeichners aus. Zweitens, für einen Bezeichner außerhalb jeder Funktion (eine "globale Variable" in einem der beiden üblichen Bedeutungen), bewirkt es, dass die Deklaration eine Deklaration ist, und keine Definition, vorausgesetzt die Variable ist nicht ebenfalls initialisiert.

Für Variablen innerhalb einer Funktion (d. h. mit "block scope"), z. B. somevar in:

%Vor%

Das Schlüsselwort extern bewirkt, dass der Bezeichner eine Verknüpfung (intern oder extern) anstelle von "keine Verknüpfung" aufweist (wie bei lokalen Variablen mit automatischer Dauer). Dabei bewirkt die Variable selbst, dass sie eine statische Dauer hat und nicht automatisch. (Variablen mit automatischer Dauer haben keine Verknüpfung und haben immer den Blockbereich und nicht den Dateibereich.)

Wie bei den Funktionsdeklarationen ist die Verknüpfung extern intern intern, wenn eine vorherige interne Deklaration für die interne Verknüpfung vorhanden ist, andernfalls extern. Das x in f() hat hier eine interne Verknüpfung, trotz des extern Schlüsselworts:

%Vor%

Der einzige Grund, diese Art von Code zu schreiben, ist, andere Programmierer zu verwirren, also tu es nicht. : -)

Im Allgemeinen besteht der Grund für die Annotation von "globalen" Variablen (d. h. Dateibereich, statische Dauer, externe Verknüpfung) mit dem Schlüsselwort extern , dass diese bestimmte Deklaration nicht zu einer Definition wird. C-Compiler, die das sogenannte "def / ref" -Modell verwenden, erhalten eine Verdauungsstörung zur Verknüpfungszeit, wenn derselbe Name mehr als einmal definiert ist. Also, wenn file1.c sagt int globalvar; und file2.c sagt auch int globalvar; , beide sind Definitionen und der Code kann nicht kompilieren (obwohl die meisten Unix-ähnliche Systeme standardmäßig das sogenannte "gemeinsame Modell" verwenden, was macht das funktioniert sowieso). Wenn Sie eine solche Variable in einer Headerdatei deklarieren, die wahrscheinlich aus vielen verschiedenen .c -Dateien eingefügt wird, verwenden Sie extern , um diese Deklaration als "nur eine Deklaration" zu definieren.

Eine und nur eine dieser .c -Dateien kann dann die Variable erneut deklarieren, wobei das extern -Schlüsselwort weggelassen wird und / oder ein Initialisierer eingeschlossen wird. Oder, einige Leute bevorzugen einen Stil, in dem die Header-Datei so etwas verwendet:

%Vor%

In diesem Fall kann eine (und nur eine) dieser .c -Dateien die Sequenz enthalten:

%Vor%

Da hier EXTERN definiert ist, deaktiviert #ifndef die nachfolgende #define und die Zeile EXTERN int globalvar; wird auf nur int globalvar; erweitert, so dass dies zu einer Definition und nicht zu einer Deklaration wird. Persönlich mag ich diesen Kodierungsstil nicht, obwohl er das Prinzip "Do not repeat yourself" erfüllt. Meistens finde ich den Großbuchstaben EXTERN irreführend, und dieses Muster ist bei der Initialisierung nicht hilfreich. Diejenigen, die es bevorzugen, fügen normalerweise ein zweites Makro hinzu, um die Initialisierer zu verbergen:

%Vor%

aber selbst dies fällt auseinander, wenn das zu initialisierende Element einen zusammengesetzten Initialisierer benötigt (z. B. struct , das auf { 42, 23, 17, "hike!" } initialisiert werden sollte).

(Anmerkung: Ich habe hier absichtlich die ganze "vorläufige Definition" beschönigt. Eine Definition ohne Initialisierer wird nur "vorläufig definiert" bis zum Ende der Übersetzungseinheit.Dies ermöglicht bestimmte Arten von Vorwärtsreferenzen, die sonst zu schwierig sind. Es ist normalerweise nicht sehr wichtig.)

einschließlich des Headers, der die Funktion f im Code deklariert, der die Funktion f definiert

Das ist immer eine gute Idee, aus einem einfachen Grund: Der Compiler vergleicht die Deklaration von f() in der Kopfzeile mit der Definition von f() in der Code. Wenn die beiden nicht übereinstimmen (aus irgendeinem Grund - in der Regel ein Fehler in der ursprünglichen Codierung oder ein Fehler bei der Aktualisierung einer der beiden während der Wartung, aber gelegentlich einfach aufgrund von Cat Walk On Keyboard Syndrom oder ähnliches), kann der Compiler den Fehler erkennen zur Kompilierzeit.

1 Der C-Standard von 1999 besagt, dass das Auslassen des Schlüsselworts extern in einer Funktionsdeklaration dasselbe bedeutet wie die Verwendung des Schlüsselworts extern . Dies ist viel einfacher zu beschreiben und bedeutet, dass Sie ein definiertes (und vernünftiges) Verhalten anstelle von undefiniertem (und daher vielleicht-guten, vielleicht-schlechten Verhalten) erhalten.

    
torek 11.08.2013 15:32
quelle

Tags und Links