it-swarm.com.de

Wie verwende ich extern, um Variablen zwischen Quelldateien auszutauschen?

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

Dies hängt mit der gemeinsamen Nutzung von Variablen in Quelldateien zusammen, aber wie funktioniert das genau? Wo verwende ich extern?

917
shilpa

Die Verwendung von extern ist nur relevant, wenn das von Ihnen erstellte Programm aus mehreren miteinander verknüpften Quelldateien besteht, wobei einige der Variablen, die beispielsweise in der Quelldatei file1.c definiert sind, in einer anderen Quelle referenziert werden müssen Dateien wie file2.c.

Es ist wichtig, den Unterschied zwischen der Definition einer Variablen und der Deklaration einer Variablen zu verstehen =:

  • Eine Variable wird deklariert , wenn der Compiler darüber informiert wird, dass eine Variable existiert (und dies ist ihr Typ). Zu diesem Zeitpunkt wird der Speicher für die Variable nicht zugewiesen.
  • Eine Variable wird definiert , wenn der Compiler den Speicher für die Variable zuweist.

Sie können eine Variable mehrmals deklarieren (obwohl ein einziges Mal ausreicht). Sie können es innerhalb eines bestimmten Bereichs nur einmal definieren. Eine Variablendefinition ist auch eine Deklaration, aber nicht alle Variablendeklarationen sind Definitionen.

Beste Möglichkeit, globale Variablen zu deklarieren und zu definieren

Die saubere, zuverlässige Möglichkeit, globale Variablen zu deklarieren und zu definieren, besteht darin, eine Header-Datei zu verwenden, die eine extern -Deklaration der Variablen enthält.

Der Header ist in der einen Quelldatei enthalten, die die Variable definiert, sowie in allen Quelldateien, die auf die Variable verweisen. Für jedes Programm definiert eine Quelldatei (und nur eine Quelldatei) die Variable. Ebenso sollte eine Header-Datei (und nur eine Header-Datei) die Variable deklarieren. Die Header-Datei ist entscheidend; Es ermöglicht die Gegenprüfung zwischen unabhängigen TUs (Übersetzungseinheiten - Think Source Files) und stellt die Konsistenz sicher.

Obwohl es andere Möglichkeiten gibt, ist diese Methode einfach und zuverlässig. Dies wird durch file3.h, file1.c und file2.c demonstriert:

file3.h

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

file1.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++; }

file2.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, globale Variablen zu deklarieren und zu definieren.


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

Die gezeigten vollständigen Programme verwenden Funktionen, daher haben sich Funktionsdeklarationen eingeschlichen. Sowohl C99 als auch C11 erfordern, dass Funktionen deklariert oder definiert werden, bevor sie verwendet werden (während C90 dies aus guten Gründen nicht tat). Ich verwende das Schlüsselwort extern vor Funktionsdeklarationen in Headern, um die Konsistenz zu gewährleisten - um es mit dem Schlüsselwort extern vor Variablendeklarationen in Headern abzugleichen. Viele Leute bevorzugen es, extern nicht vor Funktionsdeklarationen zu verwenden. Dem Compiler ist das egal - und letztendlich auch nicht, solange Sie konsistent sind, zumindest innerhalb einer Quelldatei.

prog1.h

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

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.

Die Datei prog1.mk ist nur ein Makefile für prog1. Es wird mit den meisten Versionen von make funktionieren, die seit der Jahrtausendwende hergestellt wurden. Es ist nicht spezifisch an GNU Make gebunden.

prog1.mk

# Minimal makefile for prog1

PROGRAM = prog1
FILES.c = prog1.c file1.c file2.c
FILES.h = prog1.h file3.h
FILES.o = ${FILES.c:.c=.o}

CC      = gcc
SFLAGS  = -std=c11
GFLAGS  = -g
OFLAGS  = -O3
WFLAG1  = -Wall
WFLAG2  = -Wextra
WFLAG3  = -Werror
WFLAG4  = -Wstrict-prototypes
WFLAG5  = -Wmissing-prototypes
WFLAGS  = ${WFLAG1} ${WFLAG2} ${WFLAG3} ${WFLAG4} ${WFLAG5}
UFLAGS  = # Set on command line only

CFLAGS  = ${SFLAGS} ${GFLAGS} ${OFLAGS} ${WFLAGS} ${UFLAGS}
LDFLAGS =
LDLIBS  =

all:    ${PROGRAM}

${PROGRAM}: ${FILES.o}
    ${CC} -o [email protected] ${CFLAGS} ${FILES.o} ${LDFLAGS} ${LDLIBS}

prog1.o: ${FILES.h}
file1.o: ${FILES.h}
file2.o: ${FILES.h}

# If it exists, prog1.dSYM is a directory on macOS
DEBRIS = a.out core *~ *.dSYM
RM_FR  = rm -fr

clean:
    ${RM_FR} ${FILES.o} ${PROGRAM} ${DEBRIS}

Richtlinien

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

  • Eine Header-Datei enthält nur extern Variablendeklarationen - niemals static oder nicht qualifizierte Variablendefinitionen.
  • Für jede Variable wird sie von nur einer Header-Datei deklariert (SPOT - Single Point of Truth).
  • Eine Quelldatei enthält niemals extern Variablendeklarationen - Quelldateien enthalten immer den (einzigen) Header, der sie deklariert.
  • Für jede Variable definiert genau eine Quelldatei die Variable und initialisiert sie vorzugsweise auch. (Obwohl es nicht notwendig ist, explizit auf Null zu initialisieren, schadet dies nicht und kann etwas Gutes bewirken, da es in einem Programm nur eine initialisierte Definition einer bestimmten globalen Variablen 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 dies möglich ist. Verwenden Sie stattdessen Funktionen.

Der Quellcode und der Text dieser Antwort befinden sich in meinem SOQ (Stack Overflow Questions) -Repository auf GitHub im ) src/so-0143-3204 Unterverzeichnis.

Wenn Sie kein erfahrener C-Programmierer sind, können (und sollten) Sie hier aufhören zu lesen.

Nicht so gute Möglichkeit, globale Variablen zu definieren

Mit einigen (in der Tat vielen) C-Compilern kommen Sie auch mit einer so genannten "allgemeinen" Definition einer Variablen klar. 'Common' bezieht sich hier auf eine Technik, die in Fortran zum Teilen von Variablen zwischen Quelldateien unter Verwendung eines (möglicherweise benannten) COMMON-Blocks verwendet wird. Was hier passiert, ist, dass jede von mehreren Dateien eine vorläufige Definition der Variablen liefert. Solange nicht mehr als eine Datei eine initialisierte Definition enthält, haben 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" - es ist offiziell undefiniertes Verhalten:

J.2 Undefiniertes Verhalten

Ein Bezeichner mit externer Verknüpfung wird verwendet, aber im Programm gibt es nicht genau eine externe Definition für den Bezeichner, oder der Bezeichner wird nicht verwendet, und es gibt mehrere externe Definitionen für den Bezeichner (6.9).

§6.9 Externe Definitionen ¶5

Eine externe Definition ist eine externe Deklaration, die auch eine Definition einer Funktion (außer einer Inline-Definition) oder eines Objekts ist. Wenn ein mit externer Verknüpfung deklarierter Bezeichner in einem Ausdruck verwendet wird (der nicht Teil des Operanden eines Operators sizeof oder _Alignof ist, dessen Ergebnis eine Ganzzahlkonstante ist), muss er sich irgendwo im gesamten Programm befinden genau eine externe Definition für die Kennung; Andernfalls darf es nicht mehr als einen geben.161)

161) Wenn also ein mit externer Verknüpfung deklarierter Bezeichner in einem Ausdruck nicht verwendet wird, muss keine externe Definition dafür vorhanden sein.

Der C-Standard listet ihn jedoch auch im informativen Anhang J als eine der Common extensions auf.

J.5.11 Mehrere externe Definitionen

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

Da diese Technik nicht immer unterstützt wird, ist es am besten, sie nicht zu verwenden , insbesondere wenn Ihr Code portierbar sein muss . Mit dieser Technik kann es auch zu ungewolltem Schreiben kommen. Wenn eine der Dateien i als double statt als int deklariert würde, würden Cs typunsichere Linker die Nichtübereinstimmung wahrscheinlich nicht erkennen. Wenn Sie sich auf einem Computer mit den 64-Bit-Versionen int und double befinden, wird nicht einmal eine Warnung angezeigt. Auf einem Computer mit 32-Bit int und 64-Bit double erhalten Sie wahrscheinlich eine Warnung zu den verschiedenen Größen - der Linker würde die größte Größe verwenden, genau wie ein Fortran-Programm die verwenden würde größte Größe aller gängigen Blöcke.


Die nächsten beiden 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 hier und in meiner Antwort auf eine ähnliche - Frage angegeben, führt die Verwendung mehrerer Definitionen für eine globale Variable zu undefiniertem Verhalten (J.2; §6.9) ist die Art und Weise, wie der Standard sagt, 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ück haben, als Sie verdienen". Ein Programm, das sich auf mehrere Definitionen einer externen Variablen stützt - mit oder ohne das explizite Schlüsselwort 'extern' -, ist jedoch kein streng konformes Programm und funktioniert garantiert nicht überall. Äquivalent: Es enthält einen Fehler, der sich möglicherweise selbst zeigt oder nicht.

Verstöße gegen die Richtlinien

Es gibt natürlich viele Möglichkeiten, wie diese Richtlinien verletzt werden können. Gelegentlich kann es einen guten Grund geben, die Richtlinien zu brechen, aber solche Gelegenheiten sind äußerst ungewöhnlich.

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. Wie bereits erwähnt, funktioniert dies häufig, aber der C-Standard garantiert nicht, dass dies funktioniert.

broken_header.h

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

Anmerkung 2: Wenn der Header die Variable definiert und initialisiert, kann nur eine Quelldatei in einem bestimmten Programm den Header verwenden. Da Header in erster Linie zum Teilen von Informationen dienen, ist es ein bisschen albern, einen zu erstellen, der nur einmal verwendet werden kann.

selten_korrigieren.h

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

Anmerkung 3: Wenn der Header eine statische Variable definiert (mit oder ohne Initialisierung), erhält jede Quelldatei eine eigene private Version der 'globalen' Variablen.

Wenn die Variable beispielsweise tatsächlich ein komplexes Array ist, kann dies zu einer extremen Verdoppelung des Codes führen. Es kann sehr gelegentlich sinnvoll sein, eine Wirkung zu erzielen, aber das ist sehr ungewöhnlich.


Zusammenfassung

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

Ähnliche Bedenken ergeben sich bei der Deklaration und Definition von Funktionen - es gelten analoge Regeln. Die Frage bezog sich jedoch speziell auf Variablen, sodass ich die Antwort nur auf Variablen behalten habe.

Ende der ursprünglichen Antwort

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


Late Major Addition

Vermeidung von Code-Duplikaten

Eine Sorge, die manchmal (und zu Recht) in Bezug auf den hier beschriebenen Mechanismus "Deklarationen in Kopfzeilen, Definitionen in Quelle" aufgeworfen wird, ist, dass zwei Dateien synchron gehalten werden müssen - die Kopfzeile und die Quelle. Daran schließt sich in der Regel die Beobachtung an, dass ein Makro verwendet werden kann, sodass der Header eine doppelte Aufgabe erfüllt - normalerweise werden die Variablen deklariert. Wenn jedoch ein bestimmtes Makro festgelegt wird, bevor der Header enthalten ist, werden stattdessen die Variablen definiert.

Ein weiteres Problem kann sein, dass die Variablen in einer Reihe von Hauptprogrammen definiert werden müssen. Dies ist normalerweise ein falsches Anliegen. Sie können einfach eine C-Quelldatei einführen, um die Variablen zu definieren und die mit jedem der Programme erstellte Objektdatei zu verknüpfen.

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

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++; }

file2a.c

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

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

Die nächsten beiden 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 bei diesem Schema ist, dass es keine Initialisierung der globalen Variablen bietet. Mit C99 oder C11 und Variablenargumentlisten für Makros können Sie ein Makro definieren, das auch die Initialisierung unterstützt. (Mit C89 und ohne Unterstützung für Listen mit variablen Argumenten in Makros gibt es keine einfache Möglichkeit, mit beliebig langen Initialisierern umzugehen.)

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 });

Inhalt der Blöcke #if und #else umkehren, Fehlerbehebung durch Denis Kniazhev

file1b.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 er veranschaulicht den Punkt. Das erste Argument für den zweiten Aufruf von INITIALIZER ist { 41 und das verbleibende Argument (in diesem Beispiel singulär) ist 43 }. Ohne C99 oder ähnliche Unterstützung für Listen mit variablen Argumenten für Makros sind Initialisierer, die Kommas enthalten müssen, sehr problematisch.

Richtiger Header file3b.h eingeschlossen (anstelle von fileba.h) per Denis Kniazhev


Die nächsten beiden 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.

Header Guards

Jeder Header sollte gegen Wiedereinschluss geschützt werden, damit Typdefinitionen (Enum-, Struct- oder Union-Typen oder Typedefs im Allgemeinen) keine Probleme verursachen. Die Standardtechnik besteht darin, den Hauptteil des Headers in einen Header-Guard zu wickeln, z.

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

Der Header kann indirekt zweimal enthalten sein. Wenn zum Beispiel file4b.hfile3b.h für eine Typdefinition enthält, die nicht angezeigt wird, und file1b.c beide Header file4b.h und file3b.h verwenden muss, dann Sie Es sind noch einige knifflige Probleme zu lösen. Natürlich könnten Sie die Header-Liste so überarbeiten, dass sie nur noch file4b.h enthält. Möglicherweise kennen Sie jedoch die internen Abhängigkeiten nicht - und der Code sollte im Idealfall weiterhin funktionieren.

Darüber hinaus wird es schwierig, da Sie möglicherweise file4b.h einschließen, bevor Sie file3b.h einschließen, um die Definitionen zu generieren. Die normalen Header-Guards in file3b.h verhindern jedoch, dass der Header erneut eingebunden wird.

Daher müssen Sie den Body von file3b.h höchstens einmal für Deklarationen und höchstens einmal für Definitionen einschließen. Möglicherweise benötigen Sie jedoch beide in einer einzigen Übersetzungseinheit (TU - eine Kombination aus einer Quelldatei und den zugehörigen Headern) Verwendet).

Mehrfachaufnahme mit Variablendefinitionen

Dies kann jedoch unter nicht allzu unvernünftigen Bedingungen geschehen. Lassen Sie uns einen neuen Satz von Dateinamen einführen:

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

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

Die Einschränkungen dafür sind:

  1. Der Header, der die globalen Variablen definiert oder deklariert, darf selbst keinen Typ definieren.
  2. Unmittelbar bevor Sie einen Header einfügen, der Variablen definieren soll, definieren Sie das Makro DEFINE_VARIABLES.
  3. Der Header, der die Variablen definiert oder deklariert, hat stilisierten Inhalt.

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 (liefert ein Hauptprogramm) 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. Ein Problem tritt nur dann auf, wenn ein Header, der Variablen definiert (z. B. file2c.h), in einem anderen Header (z. B. file7c.h) enthalten ist, der Variablen definiert. Es gibt keinen einfachen Weg, außer "Tu es nicht".

Sie können das Problem teilweise umgehen, indem Sie file2c.h in file2d.h überarbeiten:

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 enthalten?" Wenn Sie dies in der Kopfzeile weglassen und einen definierenden Aufruf mit #define und #undef abschließen:

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

im Quellcode (so ändern die Header niemals den Wert von DEFINE_VARIABLES), dann sollten Sie sauber sein. Es ist nur ein Ärgernis, 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 ist ein bisschen verworren, scheint aber sicher zu sein (mit dem file2d.h, ohne #undef DEFINE_VARIABLES im 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.

Es ist jedoch relativ unwahrscheinlich, dass die Probleme in der Praxis auftreten, insbesondere wenn Sie die Standardempfehlungen befolgen

Vermeiden Sie globale Variablen


Fehlt dieser Ausstellung etwas?

Geständnis : Das hier beschriebene Schema 'Vermeidung von doppeltem Code' wurde entwickelt, da das Problem einige Codes betrifft, an denen ich arbeite (die ich jedoch nicht besitze), und das Schema ist ein wenig problematisch im ersten Teil der Antwort skizziert. Mit dem ursprünglichen Schema haben Sie jedoch nur zwei Änderungsmöglichkeiten, um Variablendefinitionen und -deklarationen synchron zu halten. Dies ist ein großer Fortschritt, da externe Variablendeklarationen über die gesamte Codebasis verteilt sind (was bei insgesamt Tausenden von Dateien wirklich wichtig ist). . Der Code in den Dateien mit den Namen fileNc.[ch] (plus external.h und externdef.h) zeigt jedoch, dass er zum Funktionieren gebracht werden kann. Es ist klar, dass es nicht schwierig ist, ein Header-Generator-Skript zu erstellen, um die standardisierte Vorlage für eine Variable zu erhalten, die eine Header-Datei definiert und deklariert.

NB Hierbei handelt es sich um Spielzeugprogramme, deren Code kaum ausreicht, um sie geringfügig interessant zu machen. Es gibt Wiederholungen innerhalb der Beispiele, die entfernt werden könnten, aber die pädagogische Erklärung nicht vereinfachen sollen. (Zum Beispiel: Der Unterschied zwischen prog5.c und prog8.c ist der Name eines der enthaltenen Header. Es wäre möglich, den Code so zu reorganisieren, dass die Funktion main() nicht wiederholt wird , aber es würde mehr verbergen, als es enthüllt.)

1648

Eine Variable extern ist eine Deklaration (danke an sbi für die Korrektur) einer Variablen, die in einer anderen Übersetzungseinheit definiert ist. Das heißt, der Speicher für die Variable wird in einer anderen Datei zugeordnet.

Angenommen, Sie haben zwei .c- Dateien test1.c und test2.c. Wenn Sie eine globale Variable int test1_var; in test1.c definieren und auf diese Variable in test2.c zugreifen möchten, müssen Sie extern int test1_var; in test2.c verwenden.

Vollständige Probe:

$ 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
119
Johannes Weiss

Extern ist das Schlüsselwort, mit dem Sie deklarieren, dass sich die Variable selbst in einer anderen Übersetzungseinheit befindet.

Sie können sich also dafür entscheiden, eine Variable in einer Übersetzungseinheit zu verwenden und dann von einer anderen auf sie zuzugreifen. In der zweiten deklarieren Sie sie als extern und das Symbol wird vom Linker aufgelöst.

Wenn Sie es nicht als extern deklarieren, erhalten Sie zwei gleichnamige, aber überhaupt nicht verwandte Variablen und einen Fehler bei mehreren Definitionen der Variablen.

38
Arkaitz Jimenez

Ich stelle mir eine externe Variable gerne als ein Versprechen vor, das Sie dem Compiler geben.

Wenn der Compiler auf ein externes Objekt stößt, kann er nur dessen Typ ermitteln und nicht, wo es "lebt". Daher kann er die Referenz nicht auflösen.

Sie sagen: "Vertrauen Sie mir. Zum Zeitpunkt der Verknüpfung kann diese Referenz aufgelöst werden."

26
Buggieboy

extern weist den Compiler an, Ihnen zu vertrauen, dass der Speicher für diese Variable an einer anderen Stelle deklariert ist, sodass er nicht versucht, Speicher zuzuweisen/zu überprüfen.

Aus diesem Grund können Sie eine Datei kompilieren, die auf ein externes Objekt verweist. Sie können jedoch keine Verknüpfung herstellen, wenn dieser Speicher nicht irgendwo deklariert ist.

Nützlich für globale Variablen und Bibliotheken, aber gefährlich, da der Linker keine Prüfung eingibt.

18
BenB

Durch Hinzufügen eines extern wird eine Variable definition in eine Variable deklaration umgewandelt. Siehe dieser Thread , was der Unterschied zwischen einer Deklaration und einer Definition ist.

15
sbi

Die korrekte Interpretation von extern ist, dass Sie dem Compiler etwas mitteilen. Sie teilen dem Compiler mit, dass die deklarierte Variable, obwohl sie gerade nicht vorhanden ist, vom Linker gefunden wird (normalerweise in einem anderen Objekt (Datei)). Der Linker ist dann der Glückliche, der alles findet und zusammenfügt, unabhängig davon, ob Sie einige externe Erklärungen hatten oder nicht.

11
Alex Lockwood
                 declare | define   | initialize |
                ----------------------------------

extern int a;    yes          no           no
-------------
int a = 2019;    yes          yes          yes
-------------
int a;           yes          yes          no
-------------

Die Deklaration weist keinen Speicher zu (die Variable muss für die Speicherzuweisung definiert sein), die Definition jedoch. Dies ist nur eine weitere einfache Ansicht des externen Schlüsselworts, da die anderen Antworten wirklich großartig sind.

8
Lucian Nut

In C erhält eine Variable in einer Datei, beispielsweise example.c, einen lokalen Gültigkeitsbereich. Der Compiler erwartet, dass sich die Definition der Variablen in derselben Datei befindet, example.c, und wenn diese nicht gefunden wird, wird ein Fehler ausgegeben. Eine Funktion hat dagegen standardmäßig einen globalen Gültigkeitsbereich. Sie müssen dem Compiler also nicht explizit mitteilen, dass "look dude ... die Definition dieser Funktion finden Sie hier". Für eine Funktion genügt es, die Datei mit ihrer Deklaration einzuschließen (die Datei, die Sie eigentlich als Header-Datei bezeichnen). 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);
}

example1.c

int a = 5;

Wenn Sie nun die beiden Dateien zusammenstellen, verwenden Sie die folgenden Befehle:

schritt 1) ​​cc -o ex Beispiel.c Beispiel1.c Schritt 2) ./ ex

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

8
Phoenix225

Das Schlüsselwort extern wird zusammen mit der Variablen zur Identifizierung als globale Variable verwendet.

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

7
Anup

Implementierung von GCC ELF Linux

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

Die 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 System V ABI Update ELF-Spezifikation "Symboltabelle" erklärt:

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

dies ist im Grunde das Verhalten, das der C-Standard den Variablen extern verleiht.

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

Getestet auf GCC 4.8.

C++ 17 Inline-Variablen

In C++ 17 möchten Sie möglicherweise Inline-Variablen anstelle von externen Variablen verwenden, da diese einfach zu verwenden (können nur einmal für den Header definiert werden) und leistungsfähiger sind (Unterstützung von constexpr). Siehe: Was bedeutet "const static" in C und C++?

extern bedeutet einfach, dass eine Variable an einer anderen Stelle definiert ist (z. B. in einer anderen Datei).

5
Geremia

extern ermöglicht einem Modul Ihres Programms den Zugriff auf eine globale Variable oder Funktion, die in einem anderen Modul Ihres Programms deklariert ist. Sie haben normalerweise externe Variablen in Header-Dateien deklariert.

Wenn Sie nicht möchten, dass ein Programm auf Ihre Variablen oder Funktionen zugreift, verwenden Sie static, was dem Compiler mitteilt, dass diese Variable oder Funktion außerhalb dieses Moduls nicht verwendet werden kann.

5
loganaayahee

Zunächst wird das Schlüsselwort extern nicht zum Definieren einer Variablen verwendet. Vielmehr wird es zum Deklarieren einer Variablen verwendet. Ich kann sagen, extern ist eine Speicherklasse, kein Datentyp.

extern wird verwendet, um anderen C-Dateien oder externen Komponenten mitzuteilen, dass diese Variable bereits irgendwo definiert ist. Beispiel: Wenn Sie eine Bibliothek erstellen, müssen Sie die globale Variable nicht zwingend irgendwo in der Bibliothek selbst definieren. Die Bibliothek wird direkt kompiliert, überprüft jedoch beim Verknüpfen der Datei die Definition.

4
user1270846

extern wird verwendet, damit eine first.c -Datei vollen Zugriff auf einen globalen Parameter in einer anderen second.c -Datei hat.

Die extern kann in der first.c -Datei oder in einer der Header-Dateien first.c angegeben werden.

3
shoham

Mit xc8 müssen Sie vorsichtig sein, wenn Sie eine Variable in jeder Datei als denselben Typ deklarieren, da Sie irrtümlicherweise etwas deklarieren könnten, das int in einer Datei und char in einer anderen. Dies kann zur Beschädigung von Variablen führen.

Dieses Problem wurde elegant in einem Mikrochip-Forum vor 15 Jahren gelöst./* Siehe "http: www.htsoft.com" / / "forum/all/showflat.php/Cat/0/Number/18766/an/0/page/0 # 18766 "

Aber dieser Link scheint nicht mehr zu funktionieren ...

Also werde ich schnell versuchen, es zu erklären; Erstellen Sie eine Datei mit dem Namen global.h.

Darin erklären die folgenden

#ifdef MAIN_C
#define GLOBAL
 /* #warning COMPILING MAIN.C */
#else
#define GLOBAL extern
#endif
GLOBAL unsigned char testing_mode; // example var used in several C files

Jetzt in der Datei main.c

#define MAIN_C 1
#include "global.h"
#undef MAIN_C

Dies bedeutet, dass in main.c die Variable als unsigned char deklariert wird.

Jetzt wird es in anderen Dateien, die nur global.h enthalten, als extern für diese Datei deklariert.

extern unsigned char testing_mode;

Aber es wird korrekt als unsigned char deklariert.

Der alte Forumsbeitrag hat das wohl etwas deutlicher erklärt. Dies ist jedoch ein echtes Potenzial gotcha, wenn Sie einen Compiler verwenden, mit dem Sie eine Variable in einer Datei deklarieren und diese dann extern als anderen Typ in einer anderen Datei deklarieren können. Die damit verbundenen Probleme sind, wenn Sie testing_mode als int in einer anderen Datei deklarieren, dass es sich um eine 16-Bit-Variable handelt und einen anderen Teil von RAM überschreiben, wodurch möglicherweise eine andere Variable beschädigt wird. Schwer zu debuggen!

2
user50619

Eine sehr kurze Lösung, die ich verwende, um einer Header-Datei zu erlauben, die externe Referenz oder die tatsächliche Implementierung eines Objekts zu enthalten. Die Datei, die das Objekt tatsächlich enthält, führt nur #define GLOBAL_FOO_IMPLEMENTATION aus. Wenn ich dann ein neues Objekt zu dieser Datei hinzufüge, wird es auch in dieser Datei angezeigt, ohne dass ich die Definition kopieren und einfügen muss.

Ich verwende dieses Muster für mehrere Dateien. Um die Dinge so in sich geschlossen wie möglich zu halten, verwende ich einfach das einzelne GLOBAL-Makro in jedem Header. Mein Header sieht so aus:

//file foo_globals.h
#pragma once  
#include "foo.h"  //contains definition of foo

#ifdef GLOBAL  
#undef GLOBAL  
#endif  

#ifdef GLOBAL_FOO_IMPLEMENTATION  
#define GLOBAL  
#else  
#define GLOBAL extern  
#endif  

GLOBAL Foo foo1;  
GLOBAL Foo foo2;


//file main.cpp
#define GLOBAL_FOO_IMPLEMENTATION
#include "foo_globals.h"

//file uses_extern_foo.cpp
#include "foo_globals.h
0
muusbolla