c - titel - title tag wordpress




Wie verwende ich extern, um Variablen zwischen Quelldateien zu teilen? (10)

Ich weiß, dass globale Variablen in C manchmal das extern Schlüsselwort haben. Was ist eine extern Variable? Wie lautet die Erklärung? Was ist ihr Umfang?

Dies bezieht sich auf das Teilen von Variablen in Quelldateien, aber wie funktioniert das genau? Wo verwende ich extern ?


aber wie funktioniert das genau?

Mal sehen, wie GCC 4.8 ELF es implementiert

main.c :

#include <stdio.h>

int not_extern_int = 1;
extern int extern_int;

void main() {
    printf("%d\n", not_extern_int);
    printf("%d\n", extern_int);
}

Kompilieren und dekompilieren:

gcc -c main.c
readelf -s main.o

Ausgabe enthält:

Num:    Value          Size Type    Bind   Vis      Ndx Name
 9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 not_extern_int
12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND extern_int

Das Kapitel "Symboltabelle" des System V ABI Update ELF erklärt:

SHN_UNDEF Dieser Abschnittstabellenindex bedeutet, dass das Symbol nicht definiert ist. Wenn der Linkeditor diese Objektdatei mit einer anderen kombiniert, die das angegebene Symbol definiert, werden die Referenzen dieser Datei auf das Symbol mit der tatsächlichen Definition verknüpft.

Das ist im Grunde das Verhalten, das der C-Standard extern Variablen gibt.

Von nun an ist es die Aufgabe des Linkers, das endgültige Programm zu erstellen, aber die extern Informationen wurden bereits aus dem Quellcode in die Objektdatei extrahiert.


Die Verwendung von extern ist nur relevant, wenn das zu erstellende Programm aus mehreren miteinander verknüpften Quelldateien besteht, wobei einige der in der Quellendatei file1.c definierten Variablen in anderen Quelldateien wie file1.c müssen file2.c .

Es ist wichtig, den Unterschied zwischen dem Definieren einer Variablen und dem Deklarieren einer Variablen zu verstehen :

  • Eine Variable wird deklariert, wenn der Compiler darüber informiert wird, dass eine Variable existiert (und dies ist der Typ); Es weist den Speicher für die Variable an diesem Punkt nicht zu.
  • Eine Variable wird definiert, wenn der Compiler den Speicher für die Variable zuweist.

Sie können eine Variable mehrfach deklarieren (obwohl einmal ausreichend ist); Sie können es nur einmal innerhalb eines bestimmten Bereichs definieren. Eine Variablendefinition ist auch eine Deklaration, aber nicht alle Variablendeklarationen sind Definitionen.

Der beste Weg, globale Variablen zu deklarieren und zu definieren

Obwohl es andere Möglichkeiten gibt, dies zu tun, besteht die saubere, zuverlässige Möglichkeit, globale Variablen zu deklarieren und zu definieren, darin, eine Header-Datei file3.h zu verwenden, die eine extern Deklaration der Variablen enthält. Der Header wird von der Quelldatei eingeschlossen, die die Variable definiert, und von allen Quellendateien, die auf die Variable verweisen. Für jedes Programm definiert eine Quelldatei (und nur eine Quelldatei) die Variable. Entsprechend sollte eine Header-Datei (und nur eine Header-Datei) die Variable deklarieren.

file3.h

extern int global_variable;  /* Declaration of the variable */

Datei1.c

#include "file3.h"  /* Declaration made available here */
#include "prog1.h"  /* Function declarations */

/* Variable defined here */
int global_variable = 37;    /* Definition checked against declaration */

int increment(void) { return global_variable++; }

Datei2.c

#include "file3.h"
#include "prog1.h"
#include <stdio.h>

void use_it(void)
{
    printf("Global variable: %d\n", global_variable++);
}

Das ist der beste Weg, sie zu benutzen.

Die nächsten zwei Dateien vervollständigen die Quelle für prog1 :

Beachten Sie, dass ich das Schlüsselwort extern vor den Funktionsdeklarationen in den Headern verwende (wie zB in prog1.h ), um das extern vor den Variablendeklarationen in den Headern zu finden. Viele Menschen ziehen es vor, extern vor Funktionen zu arbeiten; Dem Compiler ist das egal - und letztendlich auch nicht, solange du konsistent bist.

prog1.h

extern void use_it(void);   // "extern" is optional here; see note above
extern int increment(void); // "extern" is optional here; see note above

prog1.c

#include "file3.h"
#include "prog1.h"
#include <stdio.h>

int main(void)
{
    use_it();
    global_variable += 19;
    use_it();
    printf("Increment: %d\n", increment());
    return 0;
}
  • prog1 verwendet prog1.c , file1.c , file2.c , file3.h und prog1.h .

Richtlinien

Regeln, die nur von Experten gebrochen werden dürfen und nur aus gutem Grund:

  • Eine Header-Datei enthält nur extern Deklarationen von Variablen - niemals static oder nicht qualifizierte Variablendefinitionen.
  • Für jede gegebene Variable deklariert nur eine Headerdatei (SPOT - Single Point of Truth).
  • Eine Quelldatei enthält niemals extern Deklarationen von Variablen - Quelldateien enthalten immer den (einzigen) Header, der sie deklariert.
  • Für jede gegebene Variable definiert genau eine Quelldatei die Variable, vorzugsweise initialisiert sie sie auch. (Obwohl es nicht nötig ist, explizit auf Null zu initialisieren, ist es nicht schädlich und kann etwas Gutes bewirken, da es nur eine initialisierte Definition einer bestimmten globalen Variablen in einem Programm geben kann).
  • Die Quelldatei, die die Variable definiert, enthält auch den Header, um sicherzustellen, dass die Definition und die Deklaration konsistent sind.
  • Eine Funktion sollte niemals eine Variable mit extern deklarieren müssen.
  • Vermeiden Sie globale Variablen wann immer möglich - verwenden Sie stattdessen Funktionen.

Wenn Sie kein erfahrener C-Programmierer sind, könnten Sie hier aufhören (und vielleicht sollten).

Der Quellcode und der Text dieser Antwort sind in meinem SOQ src/so-0143-3204 -Repository auf GitHub im Unterverzeichnis src/so-0143-3204 .

Nicht so gut, um globale Variablen zu definieren

Mit einigen (in der Tat, vielen) C-Compilern können Sie auch mit einer sogenannten "gemeinsamen" Definition einer Variablen fertig werden. "Allgemein" bezieht sich hier auf eine Technik, die in Fortran zum Teilen von Variablen zwischen Quelldateien verwendet wird, wobei ein (möglicherweise benannter) COMMON-Block verwendet wird. Was hier passiert, ist, dass jede von mehreren Dateien eine vorläufige Definition der Variablen bereitstellt. Solange nicht mehr als eine Datei eine initialisierte Definition bereitstellt, teilen sich die verschiedenen Dateien eine gemeinsame Definition der Variablen:

file10.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void inc(void) { i++; }

file11.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void dec(void) { i--; }

file12.c

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

int i = 9;   /* Do not do this in portable code */

void put(void) { printf("i = %d\n", i); }

Diese Technik entspricht nicht dem Buchstaben des C-Standards und der "Ein-Definition-Regel", aber der C-Standard listet sie als eine gemeinsame Variation ihrer Ein-Definition-Regel auf. Da diese Technik nicht immer unterstützt wird, ist es am besten, sie zu vermeiden, insbesondere wenn der Code portabel sein muss . Mit dieser Technik können Sie auch unbeabsichtigte Art Punning erhalten. Wenn eine der Dateien i als double statt als int deklarierte, würden Cs Typ-unsafe-Linker wahrscheinlich die fehlende Übereinstimmung nicht erkennen. Wenn Sie sich auf einem Computer mit 64-Bit- int und double , erhalten Sie nicht einmal eine Warnung. Auf einer Maschine mit 32-Bit- int und 64-Bit- double Sie wahrscheinlich eine Warnung über die verschiedenen Größen erhalten - der Linker würde die größte Größe verwenden, genau wie ein Fortran-Programm die größte Größe aller gängigen Blöcke hätte.

Dies wird in der C-Norm im informativen Anhang J als gemeinsame Erweiterung erwähnt:

J.5.11 Mehrere externe Definitionen

Es kann mehr als eine externe Definition für den Bezeichner eines Objekts geben, mit oder ohne explizite Verwendung des Schlüsselwortes extern; Wenn die Definitionen nicht übereinstimmen oder mehr als Eins initialisiert wird, ist das Verhalten nicht definiert (6.9.2).

Die nächsten zwei Dateien vervollständigen die Quelle für prog2 :

prog2.h

extern void dec(void);
extern void put(void);
extern void inc(void);

prog2.c

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

int main(void)
{
    inc();
    put();
    dec();
    put();
    dec();
    put();
}
  • prog2 verwendet prog2.c , file10.c , file11.c , file12.c , prog2.h .

Warnung

Wie in den Kommentaren und wie in meiner Antwort auf eine ähnliche question , führt die Verwendung mehrerer Definitionen für eine globale Variable zu einem undefinierten Verhalten, was der Standard besagt, dass "alles passieren kann". Eines der Dinge, die passieren können, ist, dass sich das Programm so verhält, wie Sie es erwarten; und J.5.11 sagt ungefähr, "Sie könnten öfter glücklich sein, als Sie verdienen". Aber ein Programm, das sich auf mehrere Definitionen einer externen Variablen stützt - mit oder ohne das explizite Schlüsselwort "extern" - ist kein streng konformes Programm und garantiert nicht überall. Äquivalent: Es enthält einen Fehler, der sich zeigen kann oder nicht.

Verletzung der Richtlinien

faulty_header.h

int some_var;    /* Do not do this in a header!!! */

Hinweis 1: Wenn der Header die Variable ohne das Schlüsselwort extern definiert, erstellt jede Datei, die den Header enthält, eine vorläufige Definition der Variablen.

kaputte_header.h

int some_var = 13;    /* Only one source file in a program can use this */

Hinweis 2: Wenn der Header die Variable definiert und initialisiert, kann nur eine Quelldatei in einem bestimmten Programm den Header verwenden.

selten_korrekt.h

static int hidden_global = 3;   /* Each source file gets its own copy  */

Hinweis 3: Wenn der Header eine statische Variable definiert (mit oder ohne Initialisierung), endet jede Quelldatei mit ihrer eigenen privaten Version der 'globalen' Variablen.

Wenn die Variable beispielsweise ein komplexes Array ist, kann dies zu extremer Codeverdopplung führen. Es kann sehr gelegentlich eine vernünftige Art sein, einen Effekt zu erzielen, aber das ist ziemlich ungewöhnlich.

Zusammenfassung

Verwenden Sie die Header-Technik, die ich zuerst gezeigt habe. Es funktioniert zuverlässig und überall. Beachten Sie insbesondere, dass der Header, der die global_variable deklariert, in jeder Datei enthalten ist, die ihn verwendet - einschließlich derjenigen, die ihn definiert. Dies stellt sicher, dass alles in sich konsistent ist.

Ähnliche Bedenken ergeben sich bei der Deklaration und Definition von Funktionen - analoge Regeln gelten. Aber die Frage lautete speziell auf Variablen, deshalb habe ich die Antwort nur auf Variablen beschränkt.

(Die vollständigen Programme benutzen Funktionen, also haben sich Funktionsdeklarationen eingeschlichen. Ich benutze das Schlüsselwort extern vor den Funktionsdeklarationen in den Headern, um das extern vor den Variablendeklarationen in den Headern zu finden. Viele bevorzugen es, extern vor Funktionen zu verwenden; dem Compiler ist es egal - und letztendlich auch nicht, solange du konsistent bist.)

Ende der ursprünglichen Antwort

Wenn Sie kein erfahrener C-Programmierer sind, sollten Sie wahrscheinlich hier aufhören zu lesen.

Späte Major Addition

Vermeidung von Code-Duplizierung

Ein Problem, das manchmal (und zu Recht) über den hier beschriebenen Mechanismus "Deklarationen im Header, Definitionen in der Quelle" aufgeworfen wird, ist, dass zwei Dateien synchronisiert werden müssen - der Header und die Quelle. Dies wird normalerweise mit einer Beobachtung verfolgt, dass ein Makro verwendet werden kann, so dass der Header eine doppelte Aufgabe erfüllt - normalerweise Deklarieren der Variablen, aber wenn ein spezifisches Makro vor dem Header gesetzt wird, definiert es stattdessen die Variablen.

Ein anderes Problem kann sein, dass die Variablen in jedem einer Reihe von "Hauptprogrammen" definiert werden müssen. Dies ist normalerweise eine falsche Sorge; Sie können einfach eine C-Quelldatei eingeben, um die Variablen zu definieren und die mit jedem Programm erstellte Objektdatei zu verknüpfen.

Ein typisches Schema funktioniert wie file3.h und verwendet die ursprüngliche globale Variable, die in file3.h :

file3a.h

#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */

EXTERN int global_variable;

file1a.c

#define DEFINE_VARIABLES
#include "file3a.h"  /* Variable defined - but not initialized */
#include "prog3.h"

int increment(void) { return global_variable++; }

Datei2a.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

void use_it(void)
{
    printf("Global variable: %d\n", global_variable++);
}

Die nächsten zwei Dateien vervollständigen die Quelle für prog3 :

prog3.h

extern void use_it(void);
extern int increment(void);

prog3.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

int main(void)
{
    use_it();
    global_variable += 19;
    use_it();
    printf("Increment: %d\n", increment());
    return 0;
}
  • prog3 verwendet prog3.c , file1a.c , file2a.c , file3a.h , prog3.h .

Variable Initialisierung

Das Problem mit diesem Schema besteht darin, dass es keine Initialisierung der globalen Variablen vorsieht. Mit C99 oder C11 und variablen Argumentlisten für Makros könnten Sie auch ein Makro definieren, das die Initialisierung unterstützt. (Mit C89 und keiner Unterstützung für Variablenargumentlisten in Makros gibt es keine einfache Möglichkeit, beliebig lange Initialisierer zu verarbeiten.)

file3b.h

#ifdef DEFINE_VARIABLES
#define EXTERN                  /* nothing */
#define INITIALIZER(...)        = __VA_ARGS__
#else
#define EXTERN                  extern
#define INITIALIZER(...)        /* nothing */
#endif /* DEFINE_VARIABLES */

EXTERN int global_variable INITIALIZER(37);
EXTERN struct { int a; int b; } oddball_struct INITIALIZER({ 41, 43 });

Der Inhalt der Blöcke #if und #else #if und der von Denis Kniazhev identifizierte Fehler behoben

Datei1b.c

#define DEFINE_VARIABLES
#include "file3b.h"  /* Variables now defined and initialized */
#include "prog4.h"

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file2b.c

#include "file3b.h"
#include "prog4.h"
#include <stdio.h>

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}

Natürlich ist der Code für die Oddball-Struktur nicht das, was Sie normalerweise schreiben würden, aber es veranschaulicht den Punkt. Das erste Argument für den zweiten Aufruf von INITIALIZER ist { 41 und das verbleibende Argument (singular in diesem Beispiel) ist 43 } . Ohne C99 oder ähnliche Unterstützung für variable Argumentlisten für Makros sind Initialisierer, die Kommas enthalten müssen, sehr problematisch.

Korrekte Header- file3b.h enthalten (anstelle von fileba.h ) pro Denis Kniazhev

Die nächsten zwei Dateien vervollständigen die Quelle für prog4 :

prog4.h

extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);

prog4.c

#include "file3b.h"
#include "prog4.h"
#include <stdio.h>

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}
  • prog4 verwendet prog4.c , file1b.c , file2b.c , prog4.h , file3b.h .

Kopfschutz

Jeder Header sollte gegen Wiedereingliederung geschützt werden, so dass Typdefinitionen (enum, struct oder union types oder typedefs im Allgemeinen) keine Probleme verursachen. Die Standardtechnik besteht darin, den Hauptteil des Headers in einen Header-Guard zu verpacken, z.

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

Der Header könnte zweimal indirekt enthalten sein. Wenn beispielsweise file4b.h file3b.h für eine Typdefinition enthält, die nicht angezeigt wird, und file1b.c beide Header file4b.h und file3b.h , müssen Sie weitere knifflige Probleme lösen. file4b.h könnten Sie die Header-Liste überarbeiten, um nur file4b.h . Möglicherweise sind Ihnen die internen Abhängigkeiten jedoch nicht bekannt - und der Code sollte im Idealfall weiter funktionieren.

Außerdem wird es schwierig, file4b.h bevor Sie file4b.h file3b.h , um die Definitionen zu erzeugen, aber die normalen Header-Wächter auf file3b.h würden verhindern, dass der Header wieder eingefügt wird.

Daher müssen Sie den Hauptteil von file3b.h höchstens einmal für Deklarationen und höchstens einmal für Definitionen file3b.h , aber Sie benötigen möglicherweise beide in einer einzelnen Übersetzungseinheit (TU - eine Kombination aus einer Quelldatei und den verwendeten Headern). .

Mehrfache Aufnahme mit Variablendefinitionen

Es kann jedoch vorbehaltlich einer nicht zu unangemessenen Einschränkung durchgeführt werden. Lassen Sie uns einen neuen Satz von Dateinamen einführen:

  • external.h für die EXTERN-Makrodefinitionen usw.
  • file1c.h , um Typen zu definieren (insbesondere, struct oddball , der Typ von oddball_struct ).
  • file2c.h zum Definieren oder file2c.h der globalen Variablen.
  • file3c.c definiert die globalen Variablen.
  • file4c.c die einfach die globalen Variablen verwendet.
  • file5c.c zeigt, dass Sie die globalen Variablen deklarieren und dann definieren können.
  • file6c.c die zeigt, dass Sie die globalen Variablen definieren und dann (versuchen) können.

In diesen Beispielen enthalten file5c.c und file6c.c den Header file2c.h mehrmals direkt, aber das ist der einfachste Weg zu zeigen, dass der Mechanismus funktioniert. Es bedeutet, dass wenn der Header indirekt zweimal enthalten wäre, dies auch sicher wäre.

Die Einschränkungen dafür sind:

  1. Der Header, der die globalen Variablen definiert oder deklariert, darf selbst keine Typen definieren.
  2. Unmittelbar bevor Sie eine Überschrift einfügen, die Variablen definieren soll, definieren Sie das Makro DEFINE_VARIABLES.
  3. Der Header, der die Variablen definiert oder deklariert, hat stilisierte Inhalte.

external.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is invoked, it redefines the macros EXTERN, INITIALIZE
** based on whether macro DEFINE_VARIABLES is currently defined.
*/
#undef EXTERN
#undef INITIALIZE

#ifdef DEFINE_VARIABLES
#define EXTERN              /* nothing */
#define INITIALIZE(...)     = __VA_ARGS__
#else
#define EXTERN              extern
#define INITIALIZE(...)     /* nothing */
#endif /* DEFINE_VARIABLES */

file1c.h

#ifndef FILE1C_H_INCLUDED
#define FILE1C_H_INCLUDED

struct oddball
{
    int a;
    int b;
};

extern void use_them(void);
extern int increment(void);
extern int oddball_value(void);

#endif /* FILE1C_H_INCLUDED */

file2c.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2C_H_DEFINITIONS)
#undef FILE2C_H_INCLUDED
#endif

#ifndef FILE2C_H_INCLUDED
#define FILE2C_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file1c.h"     /* Type definition for struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE2C_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });

#endif /* !DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */

#endif /* FILE2C_H_INCLUDED */

file3c.c

#define DEFINE_VARIABLES
#include "file2c.h"  /* Variables now defined and initialized */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file4c.c

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

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}

file5c.c

#include "file2c.h"     /* Declare variables */

#define DEFINE_VARIABLES
#include "file2c.h"  /* Variables now defined and initialized */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file6c.c

#define DEFINE_VARIABLES
#include "file2c.h"     /* Variables now defined and initialized */

#include "file2c.h"     /* Declare variables */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

Die nächste Quelldatei vervollständigt die Quelle (stellt ein Hauptprogramm bereit) für prog5 , prog6 und prog7 :

prog5.c

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

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}
  • prog5 verwendet prog5.c , file3c.c , file4c.c , file1c.h , file2c.h , external.h .
  • prog6 verwendet prog5.c , file5c.c , file4c.c , file1c.h , file2c.h , external.h .
  • prog7 verwendet prog5.c , file6c.c , file4c.c , file1c.h , file2c.h , external.h .

Dieses Schema vermeidet die meisten Probleme. Sie file2c.h nur auf ein Problem, wenn ein Header, der Variablen definiert (zB file7c.h ), von einem anderen Header (zB file7c.h ) eingeschlossen wird, der Variablen definiert. Es gibt keinen einfachen Weg um das andere als "tue es nicht".

Sie können das Problem teilweise file2c.h indem Sie file2c.h in file2d.h revidieren:

file2d.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2D_H_DEFINITIONS)
#undef FILE2D_H_INCLUDED
#endif

#ifndef FILE2D_H_INCLUDED
#define FILE2D_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file1c.h"     /* Type definition for struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE2D_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });

#endif /* !DEFINE_VARIABLES || !FILE2D_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2D_H_DEFINITIONS
#undef DEFINE_VARIABLES
#endif /* DEFINE_VARIABLES */

#endif /* FILE2D_H_INCLUDED */

Das Problem wird "sollte der Header #undef DEFINE_VARIABLES ?" Wenn Sie das aus der Kopfzeile weglassen und einen definierenden Aufruf mit #define und #undef :

#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES

im Quellcode (also ändern die Header niemals den Wert von DEFINE_VARIABLES ), dann solltest du sauber sein. Es ist nur lästig, sich daran zu erinnern, die zusätzliche Zeile zu schreiben. Eine Alternative könnte sein:

#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "externdef.h"

externdef.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is included, the macro HEADER_DEFINING_VARIABLES should
** be defined with the name (in quotes - or possibly angle brackets) of
** the header to be included that defines variables when the macro
** DEFINE_VARIABLES is defined.  See also: external.h (which uses
** DEFINE_VARIABLES and defines macros EXTERN and INITIALIZE
** appropriately).
**
** #define HEADER_DEFINING_VARIABLES "file2c.h"
** #include "externdef.h"
*/

#if defined(HEADER_DEFINING_VARIABLES)
#define DEFINE_VARIABLES
#include HEADER_DEFINING_VARIABLES
#undef DEFINE_VARIABLES
#undef HEADER_DEFINING_VARIABLES
#endif /* HEADER_DEFINING_VARIABLES */

Dies wird ein wenig verschachtelt, scheint aber sicher zu sein (mit der file2d.h , ohne #undef DEFINE_VARIABLES in der file2d.h ).

file7c.c

/* Declare variables */
#include "file2d.h"

/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"

/* Declare variables - again */
#include "file2d.h"

/* Define variables - again */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file8c.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE8C_H_DEFINITIONS)
#undef FILE8C_H_INCLUDED
#endif

#ifndef FILE8C_H_INCLUDED
#define FILE8C_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file2d.h"     /* struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE8C_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN struct oddball another INITIALIZE({ 14, 34 });

#endif /* !DEFINE_VARIABLES || !FILE8C_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE8C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */

#endif /* FILE8C_H_INCLUDED */

file8c.c

/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"

/* Define variables */
#define HEADER_DEFINING_VARIABLES "file8c.h"
#include "externdef.h"

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

Die nächsten beiden Dateien vervollständigen die Quelle für prog8 und prog9 :

prog8.c

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

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}

file9c.c

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

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}
  • prog8 verwendet prog8.c , file7c.c , file9c.c .
  • prog9 verwendet prog8.c , file8c.c , file9c.c .

Allerdings sind die Probleme in der Praxis relativ unwahrscheinlich, insbesondere wenn Sie den Standard-Ratschlag befolgen

Vermeiden Sie globale Variablen

Vermisst diese Exposition etwas?

Geständnis : Das hier skizzierte Schema "Vermeidung von Duplikaten" wurde entwickelt, weil das Problem einige Codes betrifft, an denen ich arbeite (aber nicht besitze), und es ist ein beunruhigendes Problem mit dem im ersten Teil der Antwort beschriebenen Schema. Das ursprüngliche Schema lässt jedoch nur zwei Stellen zu, die geändert werden müssen, um Variablendefinitionen und -deklarationen synchron zu halten. Dies ist ein großer Schritt vorwärts, wenn externe Variablendeklarationen über die Codebasis verstreut sind (was wirklich wichtig ist, wenn es insgesamt tausende von Dateien gibt). . Der Code in den Dateien mit den Namen fileNc.[ch] (plus external.h und externdef.h ) zeigt jedoch, dass er funktionsfähig ist. Natürlich wäre es nicht schwer, ein Headergenerator-Skript zu erstellen, das Ihnen die standardisierte Vorlage für eine Variable liefert, die die Headerdatei definiert und deklariert.

NB: Das sind Spielzeugprogramme mit gerade so wenig Code, dass sie nur marginal interessant sind. Innerhalb der Beispiele gibt es Wiederholungen, die entfernt werden könnten, aber die pädagogische Erklärung soll nicht vereinfacht werden. (Beispiel: Der Unterschied zwischen prog5.c und prog8.c ist der Name eines der Header, die enthalten sind. Es wäre möglich, den Code so zu reorganisieren, dass die main() -Funktion nicht wiederholt wird, aber mehr verbergen würde als es enthüllte.)


Durch das Hinzufügen eines extern wird eine Variablendefinition in eine Variablendeklaration umgewandelt. In diesem Thread erfahren Sie, was der Unterschied zwischen einer Deklaration und einer Definition ist.


Eine extern Variable ist eine Deklaration (dank sbi für die Korrektur) einer Variablen, die in einer anderen Übersetzungseinheit definiert ist. Das bedeutet, dass der Speicher für die Variable in einer anderen Datei zugeordnet ist.

Angenommen, Sie haben zwei .c Dateien test1.c und test2.c . Wenn Sie eine globale Variable definieren int test1_var; in test1.c und du test1.c auf diese Variable in test2.c zugreifen, test2.c du extern int test1_var; in test2.c .

Komplettes Beispiel:

$ cat test1.c 
int test1_var = 5;
$ cat test2.c
#include <stdio.h>

extern int test1_var;

int main(void) {
    printf("test1_var = %d\n", test1_var);
    return 0;
}
$ gcc test1.c test2.c -o test
$ ./test
test1_var = 5

Ich denke gerne an eine externe Variable als ein Versprechen, das Sie an den Compiler stellen.

Wenn ein Externer gefunden wird, kann der Compiler nur seinen Typ herausfinden, nicht dort, wo er "lebt", so dass er den Verweis nicht auflösen kann.

Sie sagen es, "Vertrauen Sie mir. Zur Linkzeit wird diese Referenz auflösbar sein."


In C bedeutet eine Variable in einer Datei beispiel.c lokalen Bereich. Der Compiler erwartet, dass die Variable ihre Definition innerhalb der gleichen Datei example.c hat und wenn sie nicht das gleiche findet, würde sie einen Fehler werfen. Eine Funktion dagegen hat standardmäßig einen globalen Gültigkeitsbereich. Daher müssen Sie dem Compiler nicht explizit "Look dude ..." nennen, da Sie die Definition dieser Funktion hier finden könnten. Für eine Funktion einschließlich der Datei, die ihre Deklaration enthält, ist genug. (Die Datei, die Sie tatsächlich eine Header-Datei nennen). Betrachten Sie zum Beispiel die folgenden 2 Dateien:
Beispiel.c

#include<stdio.h>
extern int a;
main(){
       printf("The value of a is <%d>\n",a);
}

Beispiel1.c

int a = 5;

Wenn Sie nun die beiden Dateien zusammen kompilieren, verwenden Sie die folgenden Befehle:

Schritt 1) ​​cc -o ex example.c example1.c Schritt 2) ./ ex

Sie erhalten folgende Ausgabe: Der Wert von a ist <5>


extern allows one module of your program to access a global variable or function declared in another module of your program. You usually have extern variables declared in header files.

If you don't want a program to access your variables or functions, you use static which tells the compiler that this variable or function cannot be used outside of this module.


extern is used so one first.c file can have full access to a global parameter in another second.c file.

The extern can be declared in the first.c file or in any of the header files first.c includes.


Externes Schlüsselwort wird mit der Variablen zur Identifizierung als globale Variable verwendet.

Es stellt auch dar, dass Sie die Variable verwenden können, die mit dem Schlüsselwort extern in jeder Datei deklariert wurde, obwohl sie in einer anderen Datei deklariert / definiert ist.


First off, the extern keyword is not used for defining a variable; rather it is used for declaring a variable. I can say extern is a storage class, not a data type.

extern is used to let other C files or external components know this variable is already defined somewhere. Example: if you are building a library, no need to define global variable mandatorily somewhere in library itself. The library will be compiled directly, but while linking the file, it checks for the definition.





extern