Frage Wie verwende ich extern, um Variablen zwischen Quelldateien zu teilen?


Ich weiß, dass globale Variablen in C manchmal die extern Stichwort. Was ist ein extern Variable? Wie lautet die Erklärung? Was ist ihr Umfang?

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


810
2017-09-16 14:08


Ursprung


Antworten:


Verwenden extern ist nur relevant, wenn das Programm erstellt wird besteht aus mehreren miteinander verknüpften Quelldateien, wobei einige der Variablen, die zum Beispiel in der Quelldatei definiert sind file1.c muss sein in anderen Quelldateien, wie z file2.c.

Es ist wichtig verstehe den Unterschied zwischen Definieren ein Variable und erklären ein Variable:

  • Eine Variable ist erklärt wenn der Compiler informiert wird, dass a Variable existiert (und das ist ihr Typ); es zuteilt nicht die Speicher für die Variable an diesem Punkt.
  • Eine Variable ist definiert wenn der Compiler den Speicher für Die Variable.

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 Variablen Deklarationen sind Definitionen.

Der beste Weg, globale Variablen zu deklarieren und zu definieren

Die saubere, zuverlässige Möglichkeit, globale Variablen zu deklarieren und zu definieren, ist zu verwenden eine Headerdatei, die ein enthält extern  Erklärung der Variable.

Der Header wird von der Quelldatei eingeschlossen, die die Variable definiert und von allen Quelldateien, die auf die Variable verweisen. Für jedes Programm definiert eine Quelldatei (und nur eine Quelldatei) die Variable. In ähnlicher Weise sollte eine Header-Datei (und nur eine Header-Datei) deklarieren Variable. Die Header-Datei ist entscheidend; es ermöglicht einen Abgleich zwischen unabhängige TUs (Übersetzungseinheiten - denke Quelldateien) und stellt sicher Konsistenz.

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

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


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

Die gezeigten vollständigen Programme benutzen Funktionen, also Funktionsdeklarationen eingeschlichen. Sowohl C99 als auch C11 erfordern, dass Funktionen deklariert oder definiert werden verwendet werden (während C90 dies aus guten Gründen nicht getan hat). Ich benutze das Schlüsselwort extern vor Funktionsdeklarationen in Headern aus Konsistenz - um den extern vor der Variable Deklarationen in Kopfzeilen. Viele Menschen bevorzugen es nicht zu benutzen extern vor der Funktion Erklärungen; Dem Compiler ist das egal - und ich 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 ein Makefile für prog1 nur. Es funktioniert mit den meisten Versionen von make produziert seit etwa der Wende des Jahrtausends. 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 $@ ${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 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 Variable deklariert sie nur eine Header-Datei (SPOT - Einzelner Punkt der Wahrheit).
  • Eine Quelldatei enthält niemals extern Deklarationen von Variablen - Quelldateien enthalten immer den (einzigen) Header, der sie deklariert.
  • Für jede Variable definiert genau eine Quelldatei die Variable vorzugsweise initialisiere ich es auch. (Obwohl es nicht nötig ist initialisiere explizit auf Null, schadet nicht und kann etwas Gutes tun, weil es nur eine initialisierte Definition eines bestimmten geben kann globale Variable in einem Programm).
  • Die Quelldatei, die die Variable definiert, enthält auch den Header to Stellen Sie sicher, dass die Definition und die Deklaration konsistent sind.
  • Eine Funktion sollte niemals eine Variable mit deklarieren müssen extern.
  • Vermeiden Sie globale Variablen wann immer möglich - verwenden Sie stattdessen Funktionen.

Der Quellcode und der Text dieser Antwort sind in meinem verfügbar        SOQ (Stapelüberlauffragen)       Repository auf GitHub in der        src / so-0143-3204       Unterverzeichnis.

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

Nicht so gut, um globale Variablen zu definieren

Mit einigen (in der Tat, vielen) C-Compilern können Sie damit durchkommen, was ist auch eine "gemeinsame" Definition einer Variablen genannt. "Allgemein" bezieht sich hier auf eine Technik, die in Fortran zum Teilen verwendet wird Variablen zwischen Quelldateien mit einem (möglicherweise benannten) COMMON-Block. Was hier passiert, ist, dass jede von einer Anzahl von Dateien vorläufig ist Definition der Variablen. Solange nicht mehr als eine Datei eine initialisierte Definition bereitstellt, dann teilen sich die verschiedenen Dateien eine gemeinsame Definition des Variable:

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 'Eine Definitionsregel' - es ist offiziell undefiniertes Verhalten:

J.2 Undefiniertes Verhalten

Eine Kennung mit externer Verknüpfung wird verwendet, aber im Programm dort   existiert nicht genau eine externe Definition für den Bezeichner oder   Der Bezeichner wird nicht verwendet und es existieren mehrere externe   Definitionen für die Kennung (6.9).

§6.9 Externe Definitionen ¶5

Ein externe Definition ist eine externe Deklaration, die auch ein   Definition einer Funktion (anders als eine Inline-Definition) oder eines   Objekt.   Wenn ein mit externer Verknüpfung deklarierter Bezeichner in einem verwendet wird   Ausdruck (außer als Teil des Operanden von a sizeof oder    _Alignof Operator, dessen Ergebnis eine ganze Zahl ist, irgendwo in   das gesamte Programm soll genau eine externe Definition für sein   die Kennung; sonst darf es nicht mehr geben als   ein.161)

161) Also, wenn ein Bezeichner mit externer Verknüpfung deklariert ist   Wird nicht in einem Ausdruck verwendet, für den es keine externe Definition geben muss   es.

Die C-Norm listet sie jedoch auch im informativen Anhang J als eine von das Gemeinsame Erweiterungen.

J.5.11 Mehrere externe Definitionen

Es kann mehr als eine externe Definition für den Bezeichner von geben   ein Objekt, mit oder ohne explizite Verwendung des Schlüsselwortes extern; ob   die Definitionen stimmen nicht überein oder es wird mehr als eine initialisiert   Verhalten ist undefiniert (6.9.2).

Da diese Technik nicht immer unterstützt wird, ist es am besten zu vermeiden es benutzen, vor allem, wenn Ihr Code portabel sein muss. Mit dieser Technik können Sie auch unbeabsichtigte Art beenden punning. Wenn eine der Dateien deklariert ist i Als ein double anstatt als ein int, Cs Typ-unsichere Linker würden wahrscheinlich das Mismatch nicht erkennen. Wenn Sie auf einem Computer mit 64-Bit sind int und double, würdest du nicht mal erhalte eine Warnung; auf einer Maschine mit 32-Bit int und 64-Bit doublewürdest du wahrscheinlich eine Warnung über die verschiedenen Größen - der Linker würde Verwenden Sie die größte Größe, genau wie ein Fortran-Programm die nehmen würde größte Größe aller gängigen Blöcke.


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 hier erwähnt, und wie in meiner Antwort auf eine ähnliche angegeben Frage, mit mehreren Definitionen für eine globale Variable führen zu undefiniertem Verhalten (J.2; §6.9), mit der die Norm sagt "alles kann passieren". Eines der Dinge, die passieren können, ist, dass sich das Programm wie Sie verhält erwarten von; und J.5.11 sagt ungefähr: "Sie könnten öfter Glück haben als du verdienst ". Aber ein Programm, das auf mehreren Definitionen einer externen Variablen beruht - mit oder ohne das explizite 'extern' Schlüsselwort - ist kein streng konformes Programm und garantiert nicht überall zu arbeiten. Äquivalent: Es enthält einen Fehler, der sich zeigen kann oder nicht.

Verletzung der Richtlinien

Es gibt natürlich viele Möglichkeiten, wie diese Richtlinien gebrochen werden können. Gelegentlich kann es einen guten Grund geben, die Richtlinien zu brechen, aber solche Anlässe 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 die definiert extern Stichwort, dann erstellt jede Datei, die den Header enthält, eine vorläufige Definition der Variable. Wie bereits erwähnt, wird dies oft funktionieren, aber der C-Standard nicht garantieren, dass es funktioniert.

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, dann nur Eine Quelldatei in einem bestimmten Programm kann den Header verwenden. Da Header in erster Linie für den Austausch von Informationen sind, ist es ein bisschen albern um eine zu erstellen, die nur einmal verwendet werden kann.

selten_korrekt.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), dann endet jede Quelldatei mit ihrer eigenen privaten Version der 'globalen' Variable.

Wenn die Variable z. B. ein komplexes Array ist, kann dies führen zu extremer Duplizierung von Code. Es kann, sehr gelegentlich, ein vernünftiger Weg, um etwas Wirkung zu erzielen, aber das ist sehr 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 den global_variable ist in jeder Datei enthalten, die es verwendet - einschließlich der, die es definiert. Dies stellt sicher, dass alles in sich konsistent ist.

Ähnliche Bedenken bestehen bei der Deklaration und Definition von Funktionen - analoge Regeln gelten. Aber die Frage lautete speziell über Variablen, also habe ich die behalten nur auf Variablen antworten.

Ende der ursprünglichen Antwort

Wenn Sie kein erfahrener C-Programmierer sind, sollten Sie hier mit dem Lesen aufhören.


Späte Major Addition

Vermeidung von Code-Duplizierung

Eine Sorge, die manchmal (und legitim) über die "Deklarationen im Header, Definitionen in Source" -Mechanismus beschrieben Hier ist, dass zwei Dateien synchronisiert werden müssen - der Header und die Quelle. Dies wird normalerweise mit einer Beobachtung verfolgt, dass a Makro kann verwendet werden, so dass der Header doppelte Aufgabe - normalerweise dient deklarieren der Variablen, aber wenn ein bestimmtes Makro vor dem gesetzt wird Header ist enthalten, definiert stattdessen die Variablen.

Ein weiteres Problem kann sein, dass die Variablen in jedem von definiert werden müssen eine Reihe von "Hauptprogrammen". Dies ist normalerweise eine falsche Sorge; Sie kann einfach eine C-Quelldatei einführen, um die Variablen und die Verknüpfung zu definieren die Objektdatei, die mit jedem der Programme erstellt wurde.

Ein typisches Schema funktioniert so und verwendet die ursprüngliche globale Variable illustriert 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, wie gezeigt, ist, dass es nicht vorsieht Initialisierung der globalen Variable. Mit C99 oder C11 und variablem Argument Listen für Makros, könnten Sie auch ein Makro definieren, das die Initialisierung unterstützt. (Bei C89 und keiner Unterstützung für Variablenargumentlisten in Makros gibt es keine einfache Möglichkeit, beliebig lange Initialisierer zu handhaben.)

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 von #if und #else Blöcke, Fehler behoben, identifiziert durch Denis Kniazhev

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

Klar, der Code für die Oddball-Struktur ist nicht das, was du normalerweise hättest schreibe, aber es illustriert den Punkt. Das erste Argument zum 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, Initializer, die dies tun müssen Kommas sind sehr problematisch.

Korrekte Kopfzeile 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 Typ Definitionen (enum, struct oder union types oder typedefs im Allgemeinen) nicht Probleme verursachen. Die Standardtechnik besteht darin, den Körper der Header in einem Header-Guard wie:

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

Der Header könnte zweimal indirekt enthalten sein. Zum Beispiel, wenn file4b.h beinhaltet file3b.h für eine Typdefinition, die nicht angezeigt wird, und file1b.cmuss beide Header verwenden file4b.h und file3b.h, dann Sie haben einige schwierigere Probleme zu lösen. Offensichtlich könnten Sie überarbeiten die Header-Liste, um nur aufzunehmen file4b.h. Sie könnten jedoch nicht sein der internen Abhängigkeiten bewusst - und der Code sollte im Idealfall arbeite weiter.

Darüber hinaus wird es schwierig, weil Sie möglicherweise hinzufügen file4b.h vor dem Einschließen file3b.h um die Definitionen zu erzeugen, aber das Normale Kopfwächter an file3b.h würde verhindern, dass der Header neu eingefügt wird.

Also, Sie müssen den Körper von einschließen file3b.h höchstens einmal für Deklarationen und höchstens einmal für Definitionen, aber Sie könnten beide benötigen in einer einzigen Übersetzungseinheit (TU - eine Kombination aus einer Quelldatei und die Header, die es verwendet).

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, die Art von oddball_struct).
  • file2c.h um die globalen Variablen zu definieren oder zu deklarieren.
  • file3c.c was definiert die globalen Variablen.
  • file4c.c die einfach die globalen Variablen verwendet.
  • file5c.c was zeigt, dass Sie die globalen Variablen deklarieren und dann definieren können.
  • file6c.c Dies zeigt, dass Sie die globalen Variablen definieren und dann (versuchen) können.

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

Die Einschränkungen dafür sind:

  1. Der Header, der die globalen Variablen definiert oder deklariert, ist möglicherweise nicht selbst Definieren Sie beliebige Typen.
  2. Unmittelbar bevor Sie eine Überschrift einfügen, die Variablen definieren sollte, Sie definieren 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 stoßen nur auf ein Problem, wenn a Header, der Variablen definiert (z. B. file2c.h) ist in enthalten ein anderer Header (sagen wir file7c.h) das definiert Variablen. Es gibt kein einfach anders herum als "tue es nicht".

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

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 enthalten #undef DEFINE_VARIABLES? ' Wenn Sie das aus der Kopfzeile weglassen und einen definierenden Aufruf mit umbrechen #define und #undef:

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

im Quellcode (so ändern die Header niemals den Wert von DEFINE_VARIABLES), dann solltest du sauber sein. Es ist nur ein Ärgernis für muss daran denken, die extra 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 file2d.h, ohne #undef DEFINE_VARIABLES in dem 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 zwei 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.

In der Praxis sind diese Probleme jedoch relativ unwahrscheinlich. vor allem, wenn Sie den Standard-Rat zu nehmen

Vermeiden Sie globale Variablen


Vermisst diese Exposition etwas?

Bekenntnis: Das hier beschriebene Schema zur Vermeidung doppelter Codes war entwickelt, weil das Problem einen Code beeinflusst, an dem ich arbeite (aber nicht besitze), und ist ein beunruhigendes Problem mit der im ersten Teil von die Antwort. Das ursprüngliche Schema lässt jedoch nur zwei übrig Zu ändernde Bereiche, um Variablendefinitionen und Deklarationen beizubehalten synchronisiert, was einen großen Schritt vorwärts gegenüber einer externen Variable darstellt Deklarationen über die Codebasis verteilt (was wirklich zählt wenn es insgesamt Tausende von Dateien gibt). Der Code in der Dateien mit den Namen fileNc.[ch] (Plus external.h und externdef.h) zeigt, dass es funktionieren kann. Natürlich wäre es nicht schwer zu sein Erstellen Sie ein Header-Generator-Skript, um Ihnen die standardisierte Vorlage zu geben für eine Variable, die Header-Dateien definiert und deklariert.

NB Das sind Spielzeugprogramme mit gerade noch genug Code, um sie zu machen marginal interessant. In den Beispielen gibt es Wiederholungen könnte entfernt werden, soll aber die pädagogische Erklärung nicht vereinfachen. (Zum Beispiel: der Unterschied zwischen prog5.c und prog8.c ist der Name von einem der Header, die enthalten sind. Es wäre möglich zu reorganisiere den Code so, dass der main() Funktion wurde nicht wiederholt, aber es würde mehr verbergen als es enthüllte.)


1482
2017-09-16 14:37



Ein 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.

Sag, du hast zwei .c-Dateien test1.c und test2.c. Wenn Sie eine globale Variable definieren int test1_var; im test1.c und Sie möchten auf diese Variable in zugreifen test2.c Sie müssen verwenden extern int test1_var; im 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

108
2017-09-16 14:12



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

Sie können also entscheiden, eine Variable in einer Übersetzungseinheit zu verwenden und dann von einer anderen auf diese zuzugreifen, dann erklären Sie sie in der zweiten als extern und das Symbol wird vom Linker aufgelöst.

Wenn Sie es nicht als extern deklarieren, erhalten Sie 2 Variablen mit dem gleichen Namen, die jedoch nicht miteinander verknüpft sind, und einen Fehler mit mehreren Definitionen der Variablen.


34
2017-09-16 14:11



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."


23
2017-09-16 14:50



extern weist den Compiler an, Ihnen zu vertrauen, dass der Speicher für diese Variable an anderer Stelle deklariert ist, also versucht er nicht, Speicher zuzuweisen / zu prüfen.

Daher können Sie eine Datei kompilieren, die auf ein Extern verweist, aber Sie können nicht verknüpfen, wenn dieser Speicher nicht irgendwo deklariert wird.

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


17
2017-09-16 14:18



Hinzufügen eines extern dreht eine Variable Definition in eine Variable Erklärung. Sehen Dieser Thread was ist der Unterschied zwischen einer Deklaration und einer Definition?


15
2017-09-16 14:16



Die korrekte Interpretation von extern ist, dass Sie dem Compiler etwas mitteilen. Sie sagen dem Compiler, dass die deklarierte Variable, obwohl sie gerade nicht vorhanden ist, irgendwie vom Linker gefunden wird (normalerweise in einem anderen Objekt (Datei)). Der Linker wird dann der Glückspilz sein, um alles zu finden und zusammenzusetzen, ob Sie äußerliche Erklärungen hatten oder nicht.


11
2018-06-20 23:43



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 Beispiel.c Beispiel1.c Schritt 2) ./ ex

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


8
2017-07-02 09:11