Antworten:
Die Verwendung extern
ist nur relevant, wenn das von Ihnen erstellte Programm aus mehreren miteinander verknüpften Quelldateien besteht, wobei einige der beispielsweise in der Quelldatei definierten Variablen file1.c
in anderen Quelldateien referenziert werden müssen, z 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 vorhanden ist (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 einmal ausreichend ist). Sie können es innerhalb eines bestimmten Bereichs nur einmal definieren. Eine Variablendefinition ist auch eine Deklaration, aber nicht alle Variablendeklarationen sind Definitionen.
Die saubere und zuverlässige Methode zum extern
Deklarieren und Definieren globaler Variablen besteht darin, eine Header-Datei zu verwenden, die eine 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-Dateien) und stellt die Konsistenz sicher.
Obwohl es andere Möglichkeiten gibt, ist diese Methode einfach und zuverlässig. Es wird gezeigt , durch file3.h
, file1.c
und file2.c
:
extern int global_variable; /* Declaration of the variable */
#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++; }
#include "file3.h"
#include "prog1.h"
#include <stdio.h>
void use_it(void)
{
printf("Global variable: %d\n", global_variable++);
}
Dies ist der beste Weg, um 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, sodass sich Funktionsdeklarationen eingeschlichen haben. 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 - damit es mit dem extern
vor Variablendeklarationen in Headern übereinstimmt. Viele Leute bevorzugen es, nicht extern
vor Funktionsdeklarationen zu verwenden; Dem Compiler ist das egal - und letztendlich auch nicht, solange Sie konsistent sind, zumindest innerhalb einer Quelldatei.
extern void use_it(void);
extern int increment(void);
#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
Anwendungen 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
seit etwa der Jahrtausendwende produziert funktionieren . Es ist nicht speziell an GNU Make gebunden.
# 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}
Regeln, die nur von Experten und nur aus gutem Grund verletzt werden dürfen:
Eine Header-Datei enthält nur extern
Deklarationen von Variablen - niemals
static
oder nicht qualifizierte Variablendefinitionen.
Für eine bestimmte Variable wird sie nur von einer Header-Datei deklariert (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 eine bestimmte Variable definiert genau eine Quelldatei die Variable und initialisiert sie vorzugsweise auch. (Obwohl es nicht erforderlich 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 deklarieren müssen extern
.
Vermeiden Sie nach Möglichkeit globale Variablen - verwenden Sie stattdessen Funktionen.
Der Quellcode und der Text dieser Antwort sind in meinem SOQ- Repository (Stack Overflow Questions) auf GitHub im Unterverzeichnis src / so-0143-3204 verfügbar .
Wenn Sie kein erfahrener C-Programmierer sind, können (und sollten) Sie hier aufhören zu lesen.
Mit einigen (in der Tat vielen) C-Compilern können Sie auch mit einer sogenannten "allgemeinen" Definition einer Variablen davonkommen. '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 einer Reihe von Dateien eine vorläufige Definition der Variablen liefert. Solange nicht mehr als eine Datei eine initialisierte Definition bereitstellt, teilen sich die verschiedenen Dateien eine gemeinsame Definition der Variablen:
#include "prog2.h"
long l; /* Do not do this in portable code */
void inc(void) { l++; }
#include "prog2.h"
long l; /* Do not do this in portable code */
void dec(void) { l--; }
#include "prog2.h"
#include <stdio.h>
long l = 9; /* Do not do this in portable code */
void put(void) { printf("l = %ld\n", l); }
Diese Technik entspricht nicht dem Buchstaben des C-Standards und der 'One Definition Rule' - es ist offiziell undefiniertes Verhalten:
Ein Bezeichner mit externer Verknüpfung wird verwendet, aber im Programm existiert nicht genau eine externe Definition für den Bezeichner, oder der Bezeichner wird nicht verwendet, und es existieren mehrere externe Definitionen für den Bezeichner (6.9).
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 (außer als Teil des Operanden eines
sizeof
oder eines_Alignof
Operators, dessen Ergebnis eine Ganzzahlkonstante ist), muss irgendwo im gesamten Programm genau eine externe Definition für den Bezeichner vorhanden sein. Andernfalls darf es nicht mehr als eine geben. 161)161) Wenn also ein mit externer Verknüpfung deklarierter Bezeichner nicht in einem Ausdruck verwendet wird, muss keine externe Definition dafür vorhanden sein.
Der C-Standard listet ihn jedoch auch im informativen Anhang J als eine der gemeinsamen Erweiterungen auf .
J.5.11 Mehrere externe Definitionen
Es kann mehr als eine externe Definition für die Kennung 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, sollten Sie sie am besten vermeiden, insbesondere wenn Ihr Code portabel sein muss . Mit dieser Technik kann es auch zu unbeabsichtigten Punns kommen.
Wenn eine der oben genannten Dateien l
als double
statt statt als deklariert long
würde, würden die typunsicheren Linker von C die Nichtübereinstimmung wahrscheinlich nicht erkennen. Wenn Sie sich auf einem 64-Bit-Computer befinden long
und double
nicht einmal eine Warnung erhalten; Auf einem Computer mit 32-Bit long
und 64-Bit erhalten double
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 größte Größe aller gängigen Blöcke annehmen würde.
Beachten Sie, dass GCC 10.1.0, das am 07.05.2020 veröffentlicht wurde, die zu verwendenden Standardkompilierungsoptionen ändert. Dies -fno-common
bedeutet, dass der obige Code standardmäßig nicht mehr verknüpft wird, es sei denn, Sie überschreiben den Standard mit -fcommon
(oder verwenden Attribute usw.). siehe den Link).
Die nächsten beiden Dateien vervollständigen die Quelle für prog2
:
extern void dec(void);
extern void put(void);
extern void inc(void);
#include "prog2.h"
#include <stdio.h>
int main(void)
{
inc();
put();
dec();
put();
dec();
put();
}
prog2
Anwendungen prog2.c
, file10.c
, file11.c
, file12.c
, prog2.h
.Wie in den Kommentaren hier erwähnt 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). Dies 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. Gleichwertig: Es enthält einen Fehler, der sich möglicherweise zeigt oder nicht.
Es gibt natürlich viele Möglichkeiten, wie diese Richtlinien gebrochen werden können. Gelegentlich kann es einen guten Grund geben, gegen die Richtlinien zu verstoßen, aber solche Anlässe sind äußerst ungewöhnlich.
c int some_var; /* Do not do this in a header!!! */
Hinweis 1: Wenn der Header die Variable ohne das extern
Schlüsselwort 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 er funktioniert.
c 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. Da Header hauptsächlich zum Teilen von Informationen dienen, ist es etwas albern, solche zu erstellen, die nur einmal verwendet werden können.
c 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), 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 Duplizierung des Codes führen. Es kann gelegentlich ein vernünftiger Weg sein, einen Effekt zu erzielen, aber das ist sehr ungewöhnlich.
Verwenden Sie die Header-Technik, die ich zuerst gezeigt habe. Es funktioniert zuverlässig und überall. Beachten Sie insbesondere, dass der Header, der das deklariert, global_variable
in jeder Datei enthalten ist, die es verwendet - einschließlich derjenigen, die es definiert. Dies stellt sicher, dass alles selbstkonsistent ist.
Ähnliche Bedenken ergeben sich bei der Deklaration und Definition von Funktionen - es gelten analoge Regeln. Da es sich jedoch speziell um Variablen handelte, habe ich die Antwort nur auf Variablen beibehalten.
Wenn Sie kein erfahrener C-Programmierer sind, sollten Sie hier wahrscheinlich aufhören zu lesen.
Späte Hauptaddition
Ein Problem, das manchmal (und zu Recht) in Bezug auf den hier beschriebenen Mechanismus "Deklarationen in Headern, Definitionen in der Quelle" geäußert wird, besteht darin, dass zwei Dateien synchronisiert werden müssen - der Header und die Quelle. Darauf folgt normalerweise die Beobachtung, dass ein Makro verwendet werden kann, damit der Header die 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 einem von mehreren 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 folgendermaßen unter Verwendung der ursprünglichen globalen Variablen, dargestellt in file3.h
:
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable;
#define DEFINE_VARIABLES
#include "file3a.h" /* Variable defined - but not initialized */
#include "prog3.h"
int increment(void) { return global_variable++; }
#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
:
extern void use_it(void);
extern int increment(void);
#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
Anwendungen prog3.c
, file1a.c
, file2a.c
, file3a.h
, prog3.h
.Das gezeigte Problem bei diesem Schema besteht darin, dass es keine Initialisierung der globalen Variablen vorsieht. Mit C99 oder C11 und variablen Argumentlisten 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.)
#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 #if
und #else
Blöcke umkehren , Fehler beheben, der von Denis Kniazhev identifiziert wurde
#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; }
#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
is { 41
und das verbleibende Argument (in diesem Beispiel Singular) ist 43 }
. Ohne C99 oder ähnliche Unterstützung für variable Argumentlisten für Makros sind Initialisierer, die Kommas enthalten müssen, sehr problematisch.
Richtiger Header file3b.h
enthalten (anstelle von fileba.h
) pro
Denis Kniazhev
Die nächsten beiden Dateien vervollständigen die Quelle für prog4
:
extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);
#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
Anwendungen prog4.c
, file1b.c
, file2b.c
, prog4.h
, file3b.h
.Jeder Header sollte vor erneuter Einschließung geschützt werden, damit Typdefinitionen (Enum-, Struktur- oder Unionstypen oder Typedefs im Allgemeinen) keine Probleme verursachen. Die Standardtechnik besteht darin, den Kopf des Headers in einen Header-Schutz zu wickeln, wie z.
#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED
...contents of header...
#endif /* FILE3B_H_INCLUDED */
Der Header kann zweimal indirekt enthalten sein. Wenn beispielsweise file4b.h
Includes file3b.h
für eine Typdefinition enthalten sind, die nicht angezeigt wird und file1b.c
sowohl den Header file4b.h
als auch verwenden muss file3b.h
, müssen Sie einige schwierigere Probleme lösen. Natürlich können Sie die Header-Liste so überarbeiten, dass sie nur enthält file4b.h
. Möglicherweise sind Sie sich der internen Abhängigkeiten jedoch nicht bewusst - und der Code sollte im Idealfall weiterhin funktionieren.
Außerdem wird es schwierig, weil Sie möglicherweise file4b.h
vor dem Einfügen einschließen file3b.h
, um die Definitionen zu generieren, aber die normalen Header- file3b.h
Schutzfunktionen würden verhindern, dass der Header wieder eingeschlossen wird.
Sie müssen also den Hauptteil von file3b.h
höchstens einmal für Deklarationen und höchstens einmal für Definitionen einschließen, benötigen jedoch möglicherweise beide in einer einzigen Übersetzungseinheit (TU - eine Kombination aus einer Quelldatei und den verwendeten Headern).
Dies kann jedoch unter nicht zu unangemessenen Bedingungen erfolgen. Lassen Sie uns einen neuen Satz von Dateinamen einführen:
external.h
für die EXTERN-Makrodefinitionen usw.
file1c.h
Typen zu definieren (insbesondere struct oddball
den Typ von oddball_struct
).
file2c.h
um die globalen Variablen zu definieren oder zu deklarieren.
file3c.c
welches die globalen Variablen definiert.
file4c.c
die einfach die globalen Variablen verwendet.
file5c.c
Dies 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 file2c.h
mehrmals einschließen , aber das ist der einfachste Weg, um zu zeigen, dass der Mechanismus funktioniert. Dies bedeutet, dass der Header auch sicher wäre, wenn er indirekt zweimal enthalten wäre.
Die Einschränkungen dafür sind:
Der Header, der die globalen Variablen definiert oder deklariert, definiert möglicherweise selbst keine Typen.
Unmittelbar bevor Sie einen Header einfügen, der Variablen definieren soll, definieren Sie das Makro DEFINE_VARIABLES.
Der Header, der die Variablen definiert oder deklariert, hat den Inhalt stilisiert.
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZE(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZE(...) /* nothing */
#endif /* DEFINE_VARIABLES */
#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 */
/* 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 */
#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; }
#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;
}
#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; }
#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
:
#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
Anwendungen prog5.c
, file3c.c
, file4c.c
, file1c.h
, file2c.h
,external.h
.
prog6
Anwendungen prog5.c
, file5c.c
, file4c.c
, file1c.h
, file2c.h
,external.h
.
prog7
Anwendungen prog5.c
, file6c.c
, file4c.c
, file1c.h
, file2c.h
, external.h
.
Dieses Schema vermeidet die meisten Probleme. Sie stoßen nur dann auf ein Problem, wenn ein Header Variablen definiert (zfile2c.h
), anderen Header ( ) enthalten ist file7c.h
, der Variablen definiert. Es gibt keinen einfachen Weg, um das zu umgehen, außer "mach es nicht".
Sie können das Problem teilweise umgehen, indem Sie es überarbeiten file2c.h
in 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 dies in der Kopfzeile weglassen und einen definierenden Aufruf mit #define
und umschließen#undef
:
#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES
im Quellcode (daher ändern die Header niemals den Wert von DEFINE_VARIABLES
), sollten Sie sauber sein. Es ist nur ein Ärgernis, sich daran erinnern zu müssen, die zusätzliche Zeile zu schreiben. Eine Alternative könnte sein:
#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 bisschen verworren, scheint aber sicher zu sein (mit dem file2d.h
, mit nein#undef DEFINE_VARIABLES
in file2d.h
).
/* 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; }
/* 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 */
/* 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
:
#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;
}
#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
Anwendungen prog8.c
, file7c.c
, file9c.c
.
prog9
Anwendungen prog8.c
, file8c.c
, file9c.c
.
Es ist jedoch relativ unwahrscheinlich, dass die Probleme in der Praxis auftreten, insbesondere wenn Sie die Standardempfehlung befolgen
Vermisst diese Ausstellung etwas?
Geständnis : Das hier beschriebene Schema "Vermeiden von doppeltem Code" wurde entwickelt, da das Problem einen Code betrifft, an dem ich arbeite (den ich aber nicht besitze), und das mit dem im ersten Teil der Antwort beschriebenen Schema ein Problem darstellt. Das ursprüngliche Schema lässt Ihnen jedoch nur zwei Stellen zum Ändern übrig, um Variablendefinitionen und Deklarationen synchron zu halten. Dies ist ein großer Fortschritt gegenüber der Verteilung externer Variablendeklarationen über die gesamte Codebasis (was wirklich wichtig ist, wenn insgesamt Tausende von Dateien vorhanden sind). . Der Code in den Dateien mit den Namen fileNc.[ch]
(plus external.h
und externdef.h
) zeigt jedoch, dass er zum Laufen gebracht werden kann. Es ist natürlich nicht schwer, ein Header-Generator-Skript zu erstellen, mit dem Sie die standardisierte Vorlage für eine Variable erhalten, die die Header-Datei definiert und deklariert.
NB Dies sind Spielzeugprogramme mit kaum genug Code, um sie geringfügig interessant zu machen. Es gibt Wiederholungen in den Beispielen, die entfernt werden könnten, aber nicht, um die pädagogische Erklärung zu vereinfachen. (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 main()
Funktion nicht wiederholt wird, aber mehr verbirgt, als sich herausstellt.)
foo.h
): #define FOO_INITIALIZER { 1, 2, 3, 4, 5 }
Definieren des Initialisierers für das Array, enum { FOO_SIZE = sizeof((int [])FOO_INITIALIZER) / sizeof(((int [])FOO_INITIALIZER)[0]) };
Abrufen der Größe des Arrays und extern int foo[];
Deklarieren des Arrays . Natürlich sollte die Definition gerecht sein int foo[FOO_SIZE] = FOO_INITIALIZER;
, obwohl die Größe nicht unbedingt in die Definition aufgenommen werden muss. Dadurch erhalten Sie eine ganzzahlige Konstante FOO_SIZE
.
Eine extern
Variable 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 zugewiesen.
.c
Angenommen, Sie haben zwei Dateien test1.c
und test2.c
. Wenn Sie definieren eine globale Variable int test1_var;
in test1.c
und Sie möchten , dass diese Variable für den Zugriff auf den test2.c
Sie verwenden haben extern int test1_var;
in test2.c
.
Komplette 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
extern int test1_var;
in ändere , int test1_var;
wird der Linker (gcc 5.4.0) weiterhin übergeben. Also, wird extern
in diesem Fall wirklich gebraucht?
extern
eine häufige Erweiterung ist, die häufig funktioniert - und speziell mit GCC funktioniert (aber GCC ist bei weitem nicht der einzige Compiler, der dies unterstützt; es ist auf Unix-Systemen weit verbreitet). Sie können in meiner Antwort nach "J.5.11" oder dem Abschnitt "Nicht so gut" suchen (ich weiß - es ist lang) und in dem Text in der Nähe, der dies erklärt (oder dies versucht).
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 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 sowie einen Fehler bei mehreren Definitionen der Variablen.
Ich stelle mir eine externe Variable gerne als Versprechen vor, das Sie dem Compiler geben.
Wenn der Compiler auf ein externes Objekt stößt, kann er nur seinen Typ herausfinden, nicht wo er "lebt", sodass er die Referenz nicht auflösen kann.
Sie sagen es: "Vertrauen Sie mir. Zum Zeitpunkt der Verknüpfung ist diese Referenz auflösbar."
extern weist den Compiler an, Ihnen zu vertrauen, dass der Speicher für diese Variable an anderer Stelle deklariert ist, sodass er nicht versucht, Speicher zuzuweisen / zu überprüfen.
Daher können Sie eine Datei kompilieren, die auf eine externe Datei 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 Typprüfung durchführt.
Hinzufügen eines extern
Windungen eine variable Definition in eine variable Deklaration . In diesem Thread erfahren Sie, was der Unterschied zwischen einer Deklaration und einer Definition ist.
int foo
und extern int foo
(Dateibereich)? Beides ist eine Erklärung, nicht wahr?
declare | define | initialize |
----------------------------------
extern int a; yes no no
-------------
int a = 2019; yes yes yes
-------------
int a; yes yes no
-------------
Durch die Deklaration wird kein Speicher zugewiesen (die Variable muss für die Speicherzuweisung definiert werden), die Definition jedoch. Dies ist nur eine weitere einfache Ansicht des externen Schlüsselworts, da die anderen Antworten wirklich großartig sind.
Die richtige 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 irgendwie gefunden wird (normalerweise in einem anderen Objekt (Datei)). Der Linker ist dann der Glückliche, der alles findet und zusammenstellt, unabhängig davon, ob Sie externe Erklärungen hatten oder nicht.
In C erhält eine Variable in einer Datei, beispielsweise example.c, einen lokalen Bereich. Der Compiler erwartet, dass die Definition der Variablen in derselben Datei example.c liegt. Wenn sie diese nicht findet, wird ein Fehler ausgegeben. Eine Funktion hingegen hat standardmäßig einen globalen Gültigkeitsbereich. Daher müssen Sie dem Compiler gegenüber nicht explizit erwähnen, dass "Look Dude ... die Definition dieser Funktion finden Sie hier". Für eine Funktion mit der Datei, die ihre Deklaration enthält, reicht dies aus. (Die Datei, die Sie tatsächlich als Header-Datei bezeichnen). Betrachten Sie beispielsweise die folgenden 2 Dateien:
example.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 Beispiel.c Beispiel1.c Schritt 2) ./ ex
Sie erhalten folgende Ausgabe: Der Wert von a ist <5>
Implementierung von GCC ELF Linux
Andere Antworten haben die Seite des Sprachgebrauchs behandelt. Schauen wir uns nun an, wie sie in dieser Implementierung implementiert wird.
Haupt 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
Im Kapitel "Symboltabelle" der System V ABI Update ELF-Spezifikation wird Folgendes erläutert:
SHN_UNDEF Dieser Abschnittstabellenindex bedeutet, dass das Symbol undefiniert ist. Wenn der Verknüpfungseditor diese Objektdatei mit einer anderen 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 extern
Variablen gibt .
Von nun an ist es Aufgabe des Linkers, das endgültige Programm zu extern
erstellen , aber die Informationen wurden 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 sind (können nur einmal im Header definiert werden) und leistungsfähiger sind (Unterstützung von constexpr). Siehe: Was bedeutet 'const static' in C und C ++?
readelf
oder nm
hilfreich sein können, haben Sie weder die Grundlagen der Verwendung erläutert extern
noch das erste Programm mit der tatsächlichen Definition abgeschlossen. Ihr Code wird nicht einmal verwendet notExtern
. Es gibt auch ein Nomenklaturproblem: Obwohl notExtern
hier definiert und nicht mit deklariert extern
, handelt es sich um eine externe Variable, auf die andere Quelldateien zugreifen können, wenn diese Übersetzungseinheiten eine geeignete Deklaration enthalten (was erforderlich wäre extern int notExtern;
!).
notExtern
war hässlich, hat es behoben. Über die Nomenklatur, lassen Sie mich wissen, wenn Sie einen besseren Namen haben. Natürlich wäre das kein guter Name für ein tatsächliches Programm, aber ich denke, es passt gut zur didaktischen Rolle hier.
global_def
für die hier definierte Variable und extern_ref
für die in einem anderen Modul definierte Variable gelten? Hätten sie eine angemessen klare Symmetrie? Sie landen immer noch mit int extern_ref = 57;
oder so etwas in der Datei, in der es definiert ist, daher ist der Name nicht ganz ideal, aber im Kontext der einzelnen Quelldatei ist es eine vernünftige Wahl. Nachdem extern int global_def;
in einem Header ist nicht so sehr ein Problem, so scheint es mir. Natürlich ganz bei Ihnen.
extern
Ermöglicht einem Modul Ihres Programms den Zugriff auf eine globale Variable oder Funktion, die in einem anderen Modul Ihres Programms deklariert ist. Normalerweise haben Sie externe Variablen in Header-Dateien deklariert.
Wenn Sie nicht möchten, dass ein Programm auf Ihre Variablen oder Funktionen zugreift, verwenden Sie static
diese Option, um dem Compiler mitzuteilen, dass diese Variable oder Funktion nicht außerhalb dieses Moduls verwendet werden kann.
Zunächst einmal wird das extern
Schlüsselwort 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.
extern
wird verwendet, damit eine first.c
Datei vollen Zugriff auf einen globalen Parameter in einer anderen second.c
Datei haben kann.
Das extern
kann in der first.c
Datei oder in einer der Header-Dateien deklariert werden first.c
.
extern
Deklaration in einem Header und nicht in befinden first.c
sollte. Wenn sich der Typ ändert, ändert sich auch die Deklaration. Außerdem sollte der Header, der die Variable deklariert, eingeschlossen werden, second.c
um sicherzustellen, dass die Definition mit der Deklaration übereinstimmt. Die Deklaration in der Kopfzeile ist der Klebstoff, der alles zusammenhält. Dadurch können die Dateien separat kompiliert werden, es wird jedoch sichergestellt, dass sie eine konsistente Ansicht des Typs der globalen Variablen haben.
Mit xc8 müssen Sie vorsichtig sein, wenn Sie eine Variable in jeder Datei als denselben Typ deklarieren, da Sie fälschlicherweise etwas int
in einer Datei und a deklarieren könnenchar
mitreden können. Dies kann zur Beschädigung von Variablen führen.
Dieses Problem wurde vor etwa 15 Jahren in einem Mikrochip-Forum elegant 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 Sie Folgendes
#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 deklariert wird unsigned char
.
In anderen Dateien, die einfach global.h enthalten, wird es als extern für diese Datei deklariert .
extern unsigned char testing_mode;
Aber es wird korrekt als deklariert unsigned char
.
Der alte Forumsbeitrag hat dies wahrscheinlich etwas klarer erklärt. Dies ist jedoch ein echtes Potenzial, gotcha
wenn Sie einen Compiler verwenden, mit dem Sie eine Variable in einer Datei deklarieren und in einer anderen Datei extern als einen anderen Typ deklarieren können. Die damit verbundenen Probleme sind, wenn Sie sagen, dass testing_mode in einer anderen Datei als int deklariert ist, würde dies bedeuten, dass es sich um eine 16-Bit-Variable handelt, und einen anderen Teil des RAM überschreiben, wodurch möglicherweise eine andere Variable beschädigt wird. Schwer zu debuggen!
Eine sehr kurze Lösung, mit der ich zulasse, dass eine Header-Datei die externe Referenz oder die tatsächliche Implementierung eines Objekts enthält. Die Datei, die das Objekt tatsächlich enthält, funktioniert nur #define GLOBAL_FOO_IMPLEMENTATION
. Wenn ich dann ein neues Objekt zu dieser Datei hinzufüge, wird es in dieser Datei auch angezeigt, ohne dass ich die Definition kopieren und einfügen muss.
Ich verwende dieses Muster für mehrere Dateien. Um die Dinge so eigenständig 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