Wo braucht man 'extern'?



  • Hey

    Mir ist klar, wie das keyword extern funktioniert. Allerdings verstehe ich nicht ganz wo man das effektiv braucht.
    Wenn ich selber Code schreibe, so sind die Definitionen bzw. Deklarationen von Variabeln und Funktionen in einem Header-File, das ich anschliessend inkludiere. Deswegen brauche ich kein 'extern'.

    Ist es in C oftmals der Fall, dass man eine Variable/Funktion in einer .c Datei definiert und anschliessend ueber das extern in einem anderen File deklariert? Wenn ja, welche Vorteile hat das gegenueber Header-Files?

    Mein Problem ist, dass ich das 'extern' noch nie wirklich in Code gesehen habe ausser in einem von Eiffel zu C kompiliertem, optimierten Code, bei dem ich kaum etwas verstanden habe. Deswegen kann ich mir nicht so gut vorstellen wann man das brauchen sollte.

    Hoffe ihr koennt mir da etwas auf die Spruenge helfen.



  • Wenn du globale Variablen deklarierst (was du idR nicht tun solltest), musst du das im Header mit extern machen und sie in einer Quellcodedatei einmalig ohne extern definieren. Ohne extern beschwert sich der Linker sonst, wenn die Headerdatei in mehreren Übersetzungseinheiten eingebunden wurde, weil er nicht weiß, welche Instanz er verwenden soll.

    Für Funktionsdeklarationen ist extern nicht erforderlich.



  • icarus2 schrieb:

    Wenn ich selber Code schreibe, so sind die Definitionen bzw. Deklarationen von Variabeln und Funktionen in einem Header-File, das ich anschliessend inkludiere. Deswegen brauche ich kein 'extern'.

    Variablen sollten nur in .c Dateien Definiert werden

    [quote="icarus2"]Ist es in C oftmals der Fall, dass man eine Variable/Funktion in einer .c Datei definiert und anschliessend ueber das extern in einem anderen File deklariert? Wenn ja, welche Vorteile hat das gegenueber Header-Files?[/quote

    Jedes C File wird für sich seperat übersetzt.
    Erst kommt der Preprozessor und ersetzt die #includes und #defines. Das ist reiner Textersatz. Für den Compiler entsteht daraus eine große Datei.

    Die wird dann übersetzt zu einer Object-Datei (.o oder .obj)

    Diese Object-Dateien werden dann vom Linker mit den Bilbliotheken (.lib) zu einem ausführbaren Programm (.exe oder a.out oder ...) gebunden.

    Damit der Compiler nicht bei jeder .c-Datei eine neue Variable anlegt definiert man diese nur in einer .c-Datei.
    Der Linker muss sonst raten ob es die selbe Variable oder eine andere ist.



  • Der Speicherklassenspezifizierer extern soll wie gesagt die Speicherklasse vorgeben. Im Gegensatz zu auto/static wird hier aber nicht wirklich eine Speicherklasse vorgegeben, extern dient allgemein zur Namensbekanntmachung, also quasi als Joker für den Compiler.
    Es gibt eh nur 3 Speicherklassen (automatisch,statisch,vorhanden), alles innerhalb von Codeblöcken ist (ohne explizite Vorgabe) auto, alles außerhalb ist static.
    extern verweist also immer auf bereits definierte Speicherbereiche, und die haben hierbei immer Prozeßlebensdauer.
    extern hat also nur deklarativen Charakter, keinen definierenden.
    Meist wird extern für Variablen aus anderen C-Modulen verwendet, quasi als Ersatz-include.
    Bei Funktionsdeklarationen (Prototypen) ist extern immer implizit und wird nur selten nochmal explizit hierbei verwendet.
    Es gibt als 4. Spezifizierer noch register, der wird aber nur optional vom Compiler umgesetzt.



  • Hey

    Danke erstmals fuer eure Erklaerungen. Ich glaube ich hatte das extern falsch verstanden (vielleicht immer noch).

    Wenn ich folgenden Code habe:
    Header1.h

    #ifndef HEADER_1
    #define HEADER_1
    
    double PI; // Hier muesste doch ein extern sein?
    
    #endif
    

    Implementation1.c

    #include "Header1.h"
    
    double PI = 3.141592653;
    

    Main.c

    #include <stdio.h>
    #include "Header1.h"
    
    int main()
    {
    	printf("%lf\n", PI);
    }
    

    Wenn ich eure Erklaerungen richtig verstanden habe, dann duerfte obiger Code nicht linken. Allerdings funktioniert der Code einwandfrei unter MSVC2010.



  • Wutz schrieb:

    Der Speicherklassenspezifizierer extern soll wie gesagt die Speicherklasse vorgeben. Im Gegensatz zu auto/static wird hier aber nicht wirklich eine Speicherklasse vorgegeben, extern dient allgemein zur Namensbekanntmachung, also quasi als Joker für den Compiler.
    Es gibt eh nur 3 Speicherklassen (automatisch,statisch,vorhanden), alles innerhalb von Codeblöcken ist (ohne explizite Vorgabe) auto, alles außerhalb ist static.
    extern verweist also immer auf bereits definierte Speicherbereiche, und die haben hierbei immer Prozeßlebensdauer.
    extern hat also nur deklarativen Charakter, keinen definierenden.
    Meist wird extern für Variablen aus anderen C-Modulen verwendet, quasi als Ersatz-include.
    Bei Funktionsdeklarationen (Prototypen) ist extern immer implizit und wird nur selten nochmal explizit hierbei verwendet.
    Es gibt als 4. Spezifizierer noch register, der wird aber nur optional vom Compiler umgesetzt.

    Das ist größtenteils ziemlicher Unfug, wie man im Standard leicht hätte nachlesen können. Ich beziehe mich im Folgenden auf ISO/IEC 9899:1999 (C99).

    Zunächst gibt es fünf storage-class specifiers (6.7.1), nämlich typedef, extern, static, auto und register. Typedef ist dabei ein offensichtlicher Sonderfall und keine eigentliche Speicherklasse; dass typedef als Speicherklasse gilt, ist syntaktischer Bequemlichkeit geschuldet. Die anderen vier schließen sich gegenseitig aus, decken aber auch unterschiedliche Bedeutungen ab.

    extern bezieht sich allein auf Linkage - etwas extern zu deklarieren bedeutet, ihm externe Linkage zu geben. Das bedeutet, dass ein Objekt bzw. eine Funktion auch in anderen Übersetzungseinheiten sichtbar sein soll. Im File-Scope sind Variablendeklarationen per Default extern; deswegen kriegt der Linker auch Probleme, wenn die selbe Variablendefinition in mehreren Übersetzungseinheiten auftaucht. (6.2.2 (5)) Funktionsdeklarationen sind ebenfalls per Default extern. Objekte mit externer Linkage haben immer statische Speicherdauer (6.2.4 (3)).

    Weder für Funktionen noch für Variablen verhindert extern laut Standard, dass eine Deklaration gleichzeitig Definition wird. Für Funktionen ist die Sache ganz klar; es ist ohne weiteres möglich,

    #include <stdio.h>
    
    extern int main(void) {
      puts("Hallo, Welt.");
      return 0;
    }
    

    zu schreiben, und die Deklarationen

    void foo(void);
    extern void foo(void);
    

    sind äquivalent. Für Variablen ist es de facto etwas komplizierter. Zwar ist es laut Standard durchaus möglich, extern auch in einer Definition zu verwenden:

    ISO/IEC 9899:1999 6.9.2 (4) schrieb:

    int i1 = 1;        // definition, external linkage
    static int i2 = 2; // definition, internal linkage
    extern int i3 = 3; // definition, external linkage
    int i4;            // tentative definition, external linkage
    static int i5;     // tentative definition, internal linkage
    
    int i1;            // valid tentative definition, refers to previous
    int i2;            // 6.2.2 renders undefined, linkage disagreement
    int i3;            // valid tentative definition, refers to previous
    int i4;            // valid tentative definition, refers to previous
    int i5;            // 6.2.2 renders undefined, linkage disagreement
    
    extern int i1;     // refers to previous, whose linkage is external
    extern int i2;     // refers to previous, whose linkage is internal
    extern int i3;     // refers to previous, whose linkage is external
    extern int i4;     // refers to previous, whose linkage is external
    extern int i5;     // refers to previous, whose linkage is internal
    

    ...allerdings ist das nicht üblich, und zumindest gcc wirft bei

    extern int i3 = 3;
    

    eine Warnung. Ich halte das für einen Bug. Wie dem auch sei, maßgeblich ist der Unterschied zwischen Deklaration und Definition wie in 6.7 (5) beschrieben:

    ISO/IEC 9899:1999 6.7 (5) schrieb:

    A definition of an identifier is a declaration for that identifier that:
    - for an object, causes storage to be reserved for that object;
    - for a function, includes the function body;
    - for an enumeration constant or typedef name, is the (only) declaration of the identifier.

    So sind

    void foo(void);          // Deklaration
    extern void foo(void);   // Deklaration
    extern int i;            // Deklaration
    
    void foo(void) {}        // Definition
    extern void foo(void) {} // Definition
    int i = 1;               // Definition
    extern int i = 1;        // Definition laut Standard; gängige Compiler aber anderer Ansicht.
    

    static gibt einer globalen Variable oder Funktion interne Linkage (6.2.2 (3)), d.h. sie existiert nur innerhalb der Übersetzungseinheit. Innerhalb einer Funktion gibt sie einer Variable statische Speicherdauer (6.2.4 (3)), aber keine Linkage.

    register ist eine (unverbindliche) Optimierungshilfe; sie gibt dem Compiler zu verstehen, dass der Zugriff auf diese Variable möglichst schnell sein soll. Ob und wie er das macht, ist ausdrücklich nicht geregelt. (6.7.1 (4)) register verleiht keine Linkage und hat keinen Einfluss auf die Speicherdauer. Das ist aber auch nicht notwendig, da register eh nur im lokalen Kontext angewendet werden kann. (6.9 (2)) Außerdem kann die Speicheradresse einer register-Variable nicht genommen werden, da diese keine solche haben muss. (Fußnote 100)

    Für auto wird kein besonderes Verhalten festgelegt; lokale Variablen und Funktionsparameter haben per Default diese Speicherklasse. Wie register kann auto nur im lokalen Kontext angewandt werden. (6.9 (2))



  • @icarus2:

    Das sollte so in der Tat vom Linker abgelehnt werden.

    double PI;
    

    im Header ist eine tentative definition, die am Ende von Main.c dazu führen sollte, dass ein double PI mit Wert 0 angelegt wird, während in Iplementation1.c später ein double PI mit Wert 3.141... angelegt wird. Der Linker dürfte nicht wissen, welchen der beiden er verwenden sollte. Bist du sicher, dass Implementation1.c mitgelinkt wird?

    Jedenfalls sollte das im Header

    extern double PI;
    

    oder besser noch

    extern double const PI;
    

    und in Implementation1.c

    double const PI = 3.141592654;
    

    heißen.



  • Ich denke schon, dass alles gelinkt wurde. Ich werde das vielleicht nochmals genauer anschauen.

    Ich habe gerade noch diesen Artikel durchgelesen und ich glaube, dass ich es (groesstenteils verstanden habe).

    Danke fuer die Hilfe!


Anmelden zum Antworten