L'utilisation extern
n'est pertinente que lorsque le programme que vous créez est composé de plusieurs fichiers source liés entre eux, où certaines des variables définies, par exemple, dans le fichier source file1.c
doivent être référencées dans d'autres fichiers source, tels que file2.c
.
Il est important de comprendre la différence entre définir une variable et déclarer une variable :
Une variable est déclarée lorsque le compilateur est informé qu'une variable existe (et c'est son type); il n'alloue pas le stockage pour la variable à ce stade.
Une variable est définie lorsque le compilateur alloue le stockage pour la variable.
Vous pouvez déclarer une variable plusieurs fois (bien qu'une seule fois soit suffisante); vous ne pouvez le définir qu'une seule fois dans une portée donnée. Une définition de variable est également une déclaration, mais toutes les déclarations de variable ne sont pas des définitions.
Meilleure façon de déclarer et de définir des variables globales
Le moyen propre et fiable de déclarer et de définir des variables globales consiste à utiliser un fichier d'en-tête pour contenir une extern
déclaration de la variable.
L'en-tête est inclus par le fichier source unique qui définit la variable et par tous les fichiers source qui référencent la variable. Pour chaque programme, un fichier source (et un seul fichier source) définit la variable. De même, un fichier d'en-tête (et un seul fichier d'en-tête) doit déclarer la variable. Le fichier d'en-tête est crucial; il permet le recoupement entre les unités de traitement indépendantes (unités de traduction - pensez aux fichiers source) et garantit la cohérence.
Bien qu'il existe d'autres façons de le faire, cette méthode est simple et fiable. Il est démontré par file3.h
, file1.c
et file2.c
:
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++);
}
C'est la meilleure façon de déclarer et de définir des variables globales.
Les deux fichiers suivants complètent la source de prog1
:
Les programmes complets présentés utilisent des fonctions, donc les déclarations de fonctions se sont glissées. C99 et C11 exigent que les fonctions soient déclarées ou définies avant d'être utilisées (contrairement à C90, pour de bonnes raisons). J'utilise le mot-clé extern
devant les déclarations de fonction dans les en-têtes pour la cohérence - pour faire correspondre le extern
devant les déclarations de variables dans les en-têtes. Beaucoup de gens préfèrent ne pas utiliser extern
devant les déclarations de fonction; le compilateur s'en fiche - et finalement, moi non plus tant que vous êtes cohérent, au moins dans un fichier source.
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
utilisations prog1.c
, file1.c
, file2.c
, file3.h
et prog1.h
.
Le fichier prog1.mk
est un makefile pour prog1
seulement. Il fonctionnera avec la plupart des versions de make
produites depuis environ le tournant du millénaire. Il n'est pas lié spécifiquement à GNU Make.
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}
Des lignes directrices
Les règles doivent être enfreintes uniquement par des experts, et uniquement pour une bonne raison:
Un fichier d'en-tête contient uniquement des extern
déclarations de variables - jamais
static
ou des définitions de variables non qualifiées.
Pour une variable donnée, un seul fichier d'en-tête la déclare (SPOT - Single Point of Truth).
Un fichier source ne contient jamais de extern
déclarations de variables - les fichiers source incluent toujours l'en-tête (unique) qui les déclare.
Pour toute variable donnée, exactement un fichier source définit la variable, de préférence en l'initialisant également. (Bien qu'il ne soit pas nécessaire d'initialiser explicitement à zéro, cela ne nuit pas et peut faire du bien, car il ne peut y avoir qu'une seule définition initialisée d'une variable globale particulière dans un programme).
Le fichier source qui définit la variable comprend également l'en-tête pour garantir la cohérence de la définition et de la déclaration.
Une fonction ne devrait jamais avoir besoin de déclarer une variable à l'aide de extern
.
Évitez autant que possible les variables globales - utilisez plutôt des fonctions.
Le code source et le texte de cette réponse sont disponibles dans mon référentiel SOQ (Stack Overflow Questions) sur GitHub dans le sous-répertoire src / so-0143-3204 .
Si vous n'êtes pas un programmeur C expérimenté, vous pouvez (et devriez peut-être) arrêter de lire ici.
Pas si bon moyen de définir des variables globales
Avec certains (en fait, de nombreux) compilateurs C, vous pouvez aussi vous passer de ce que l'on appelle une définition «commune» d'une variable. «Commun», ici, fait référence à une technique utilisée dans Fortran pour partager des variables entre des fichiers source, en utilisant un bloc COMMON (éventuellement nommé). Ce qui se passe ici, c'est que chacun d'un certain nombre de fichiers fournit une définition provisoire de la variable. Tant qu'un seul fichier fournit une définition initialisée, les différents fichiers finissent par partager une définition unique commune de la variable:
file10.c
#include "prog2.h"
long l; /* Do not do this in portable code */
void inc(void) { l++; }
file11.c
#include "prog2.h"
long l; /* Do not do this in portable code */
void dec(void) { l--; }
file12.c
#include "prog2.h"
#include <stdio.h>
long l = 9; /* Do not do this in portable code */
void put(void) { printf("l = %ld\n", l); }
Cette technique n'est pas conforme à la lettre de la norme C et à la «règle d'une définition» - c'est un comportement officiellement indéfini:
J.2 Comportement indéfini
Un identifiant avec liaison externe est utilisé, mais dans le programme, il n'existe pas exactement une définition externe pour l'identifiant, ou l'identifiant n'est pas utilisé et il existe plusieurs définitions externes pour l'identifiant (6.9).
§6.9 Définitions externes ¶5
Une définition externe est une déclaration externe qui est également une définition d'une fonction (autre qu'une définition en ligne) ou d'un objet. Si un identifiant déclaré avec liaison externe est utilisé dans une expression (autre que dans le cadre de l'opérande d'un opérateur sizeof
or _Alignof
dont le résultat est une constante entière), quelque part dans le programme entier, il doit y avoir exactement une définition externe pour l'identifiant; sinon, il n'y en aura pas plus d'un. 161)
161) Ainsi, si un identifiant déclaré avec un lien externe n'est pas utilisé dans une expression, il n'y a pas besoin de définition externe pour celui-ci.
Cependant, la norme C la répertorie également dans l'annexe informative J comme l'une des extensions communes .
J.5.11 Plusieurs définitions externes
Il peut y avoir plusieurs définitions externes pour l'identifiant d'un objet, avec ou sans l'utilisation explicite du mot-clé extern; si les définitions sont en désaccord, ou si plusieurs sont initialisées, le comportement n'est pas défini (6.9.2).
Cette technique n'étant pas toujours prise en charge, il vaut mieux éviter de l'utiliser, surtout si votre code doit être portable . En utilisant cette technique, vous pouvez également vous retrouver avec une punition de type non intentionnelle.
Si l'un des fichiers ci-dessus était déclaré en l
tant que double
au lieu de en tant que long
, les éditeurs de liens non sécurisés de type C ne détecteraient probablement pas le décalage. Si vous êtes sur une machine avec 64 bits long
et double
, vous ne recevrez même pas d'avertissement; sur une machine avec 32 bits long
et 64 bits double
, vous obtiendriez probablement un avertissement concernant les différentes tailles - l'éditeur de liens utiliserait la plus grande taille, exactement comme un programme Fortran prendrait la plus grande taille de tous les blocs communs.
Notez que GCC 10.1.0, qui a été publié le 2020-05-07, modifie les options de compilation par défaut à utiliser -fno-common
, ce qui signifie que par défaut, le code ci-dessus n'est plus lié sauf si vous remplacez la valeur par défaut par -fcommon
(ou utilisez des attributs, etc - voir le lien).
Les deux fichiers suivants complètent la source de 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
utilisations prog2.c
, file10.c
, file11.c
, file12.c
, prog2.h
.
Attention
Comme indiqué dans les commentaires ici, et comme indiqué dans ma réponse à une question similaire , l'utilisation de plusieurs définitions pour une variable globale conduit à un comportement indéfini (J.2; §6.9), qui est la manière standard de dire "tout peut arriver". L'une des choses qui peuvent se produire est que le programme se comporte comme vous l'attendez; et J.5.11 dit, approximativement, "vous pourriez avoir de la chance plus souvent que vous ne le méritez". Mais un programme qui repose sur plusieurs définitions d'une variable externe - avec ou sans le mot clé explicite «extern» - n'est pas un programme strictement conforme et n'est pas garanti pour fonctionner partout. De manière équivalente: il contient un bug qui peut ou non apparaître.
Violation des directives
Il existe, bien sûr, de nombreuses manières de rompre ces directives. Parfois, il peut y avoir une bonne raison de ne pas respecter les directives, mais de telles occasions sont extrêmement inhabituelles.
faulty_header.h
c int some_var; /* Do not do this in a header!!! */
Remarque 1: si l'en-tête définit la variable sans le extern
mot - clé, chaque fichier qui inclut l'en-tête crée une définition provisoire de la variable. Comme indiqué précédemment, cela fonctionnera souvent, mais la norme C ne garantit pas que cela fonctionnera.
broken_header.h
c int some_var = 13; /* Only one source file in a program can use this */
Remarque 2: si l'en-tête définit et initialise la variable, alors un seul fichier source dans un programme donné peut utiliser l'en-tête. Étant donné que les en-têtes sont principalement destinés au partage d'informations, il est un peu idiot d'en créer un qui ne peut être utilisé qu'une seule fois.
seldom_correct.h
c static int hidden_global = 3; /* Each source file gets its own copy */
Remarque 3: si l'en-tête définit une variable statique (avec ou sans initialisation), chaque fichier source se retrouve avec sa propre version privée de la variable 'globale'.
Si la variable est en fait un tableau complexe, par exemple, cela peut entraîner une duplication extrême du code. Cela peut, très occasionnellement, être un moyen sensé d'obtenir un certain effet, mais c'est très inhabituel.
Sommaire
Utilisez la technique d'en-tête que j'ai montrée en premier. Cela fonctionne de manière fiable et partout. Notez, en particulier, que l'en-tête déclarant le global_variable
est inclus dans chaque fichier qui l'utilise - y compris celui qui le définit. Cela garantit que tout est cohérent.
Des préoccupations similaires se posent avec la déclaration et la définition de fonctions - des règles analogues s'appliquent. Mais la question portait spécifiquement sur les variables, j'ai donc gardé la réponse aux variables uniquement.
Fin de la réponse originale
Si vous n'êtes pas un programmeur C expérimenté, vous devriez probablement arrêter de lire ici.
Addition majeure tardive
Éviter la duplication de code
Une préoccupation qui est parfois (et légitimement) soulevée au sujet du mécanisme «déclarations dans les en-têtes, définitions dans la source» décrit ici est qu'il y a deux fichiers à maintenir synchronisés - l'en-tête et la source. Ceci est généralement suivi d'une observation selon laquelle une macro peut être utilisée pour que l'en-tête remplisse une double fonction - déclarant normalement les variables, mais lorsqu'une macro spécifique est définie avant que l'en-tête ne soit inclus, elle définit les variables à la place.
Une autre préoccupation peut être que les variables doivent être définies dans chacun d'un certain nombre de «programmes principaux». Il s'agit normalement d'une fausse préoccupation; vous pouvez simplement introduire un fichier source C pour définir les variables et lier le fichier objet produit avec chacun des programmes.
Un schéma typique fonctionne comme ceci, en utilisant la variable globale d'origine illustrée dans 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++; }
file2a.c
#include "file3a.h"
#include "prog3.h"
#include <stdio.h>
void use_it(void)
{
printf("Global variable: %d\n", global_variable++);
}
Les deux fichiers suivants complètent la source de 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
utilisations prog3.c
, file1a.c
, file2a.c
, file3a.h
, prog3.h
.
Initialisation variable
Le problème avec ce schéma, comme indiqué, est qu'il ne prévoit pas l'initialisation de la variable globale. Avec C99 ou C11 et les listes d'arguments variables pour les macros, vous pouvez également définir une macro pour prendre en charge l'initialisation. (Avec C89 et sans prise en charge des listes d'arguments variables dans les macros, il n'y a pas de moyen facile de gérer des initialiseurs arbitrairement longs.)
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 });
Inverser le contenu #if
et les #else
blocs, correction d'un bug identifié par
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;
}
De toute évidence, le code de la structure bizarre n'est pas ce que vous écririez normalement, mais il illustre le point. Le premier argument de la deuxième invocation de INITIALIZER
is { 41
et l'argument restant (singulier dans cet exemple) est 43 }
. Sans C99 ou un support similaire pour les listes d'arguments variables pour les macros, les initialiseurs qui doivent contenir des virgules sont très problématiques.
En-tête correct file3b.h
inclus (au lieu de fileba.h
) par
Denis Kniazhev
Les deux fichiers suivants complètent la source de 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
utilisations prog4.c
, file1b.c
, file2b.c
, prog4.h
, file3b.h
.
Protecteurs d'en-tête
Tout en-tête doit être protégé contre la réinclusion, afin que les définitions de type (types énum, structure ou union, ou typedefs en général) ne causent pas de problèmes. La technique standard consiste à envelopper le corps de l'en-tête dans une protection d'en-tête telle que:
#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED
...contents of header...
#endif /* FILE3B_H_INCLUDED */
L'en-tête peut être inclus indirectement deux fois. Par exemple, si file4b.h
inclut file3b.h
pour une définition de type qui n'est pas affichée et file1b.c
doit utiliser à la fois l'en-tête file4b.h
et file3b.h
, vous avez des problèmes plus délicats à résoudre. De toute évidence, vous pouvez réviser la liste des en-têtes pour n'en inclure que file4b.h
. Cependant, vous pourriez ne pas être au courant des dépendances internes - et le code devrait, idéalement, continuer à fonctionner.
De plus, cela commence à devenir difficile car vous pouvez inclure file4b.h
avant d'inclure file3b.h
pour générer les définitions, mais les gardes d'en-tête normaux file3b.h
empêcheraient la ré-inclusion de l'en-tête.
Ainsi, vous devez inclure le corps d' file3b.h
au plus une fois pour les déclarations et au plus une fois pour les définitions, mais vous pourriez avoir besoin des deux dans une seule unité de traduction (TU - une combinaison d'un fichier source et des en-têtes qu'il utilise).
Inclusion multiple avec définitions de variables
Cependant, cela peut être fait sous réserve d'une contrainte pas trop déraisonnable. Introduisons un nouvel ensemble de noms de fichiers:
external.h
pour les définitions de macro EXTERNES, etc.
file1c.h
pour définir des types (notamment struct oddball
, le type de oddball_struct
).
file2c.h
pour définir ou déclarer les variables globales.
file3c.c
qui définit les variables globales.
file4c.c
qui utilise simplement les variables globales.
file5c.c
ce qui montre que vous pouvez déclarer puis définir les variables globales.
file6c.c
ce qui montre que vous pouvez définir puis (tenter de) déclarer les variables globales.
Dans ces exemples, file5c.c
et file6c.c
incluez directement l'en-tête file2c.h
plusieurs fois, mais c'est le moyen le plus simple de montrer que le mécanisme fonctionne. Cela signifie que si l'en-tête était indirectement inclus deux fois, il serait également sûr.
Les restrictions pour que cela fonctionne sont:
L'en-tête définissant ou déclarant les variables globales ne peut lui-même définir aucun type.
Immédiatement avant d'inclure un en-tête qui doit définir des variables, vous définissez la macro DEFINE_VARIABLES.
L'en-tête définissant ou déclarant les variables a un contenu stylisé.
external.h
#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; }
Le fichier source suivant complète la source (fournit un programme principal) pour prog5
, prog6
et 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
utilisations prog5.c
, file3c.c
, file4c.c
, file1c.h
, file2c.h
, external.h
.
prog6
utilisations prog5.c
, file5c.c
, file4c.c
, file1c.h
, file2c.h
, external.h
.
prog7
utilisations prog5.c
, file6c.c
, file4c.c
, file1c.h
, file2c.h
, external.h
.
Ce schéma évite la plupart des problèmes. Vous ne rencontrez un problème que si un en-tête qui définit des variables (comme file2c.h
) est inclus par un autre en-tête (par exemple file7c.h
) qui définit des variables. Il n'y a pas de moyen facile de contourner cela autre que "ne le faites pas".
Vous pouvez travailler en partie autour du problème en révisant file2c.h
en 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 */
Le problème devient "l'en-tête doit-il inclure #undef DEFINE_VARIABLES
?" Si vous omettez cela dans l'en-tête et encapsulez toute invocation de définition avec #define
et #undef
:
#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES
dans le code source (afin que les en-têtes ne modifient jamais la valeur de DEFINE_VARIABLES
), alors vous devriez être propre. C'est juste une nuisance d'avoir à se rappeler d'écrire la ligne supplémentaire. Une alternative pourrait être:
#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "externdef.h"
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 */
Cela devient un peu alambiqué, mais semble être sécurisé (en utilisant le file2d.h
, avec aucun #undef DEFINE_VARIABLES
dans le 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; }
Les deux fichiers suivants complètent la source de prog8
et 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
utilisations prog8.c
, file7c.c
, file9c.c
.
prog9
utilisations prog8.c
, file8c.c
, file9c.c
.
Cependant, il est peu probable que les problèmes surviennent dans la pratique, surtout si vous suivez les conseils
Évitez les variables globales
Cette exposition manque-t-elle quelque chose?
Confession : Le schéma `` éviter le code dupliqué '' décrit ici a été développé parce que le problème affecte du code sur lequel je travaille (mais qui ne m'appartient pas), et est une inquiétante préoccupation avec le schéma décrit dans la première partie de la réponse. Cependant, le schéma d'origine ne vous laisse que deux endroits à modifier pour garder les définitions et les déclarations de variables synchronisées, ce qui est un grand pas en avant par rapport aux déclarations de variables externes dispersées dans la base de code (ce qui compte vraiment quand il y a des milliers de fichiers au total) . Cependant, le code dans les fichiers avec les noms fileNc.[ch]
(plus external.h
et externdef.h
) montre qu'il peut fonctionner. De toute évidence, il ne serait pas difficile de créer un script de générateur d'en-tête pour vous donner le modèle standardisé pour un fichier d'en-tête définissant et déclarant une variable.
NB Ce sont des programmes de jouets avec à peine assez de code pour les rendre marginalement intéressants. Il y a de la répétition dans les exemples qui pourraient être supprimés, mais ce n'est pas pour simplifier l'explication pédagogique. (Par exemple: la différence entre prog5.c
et prog8.c
est le nom de l'un des en-têtes inclus. Il serait possible de réorganiser le code afin que la main()
fonction ne soit pas répétée, mais elle cacherait plus qu'elle ne révélait.)
foo.h
):#define FOO_INITIALIZER { 1, 2, 3, 4, 5 }
pour définir l'initialiseur du tableau,enum { FOO_SIZE = sizeof((int [])FOO_INITIALIZER) / sizeof(((int [])FOO_INITIALIZER)[0]) };
pour obtenir la taille du tableau etextern int foo[];
pour déclarer le tableau . De toute évidence, la définition doit être justeint foo[FOO_SIZE] = FOO_INITIALIZER;
, bien que la taille ne doive pas vraiment être incluse dans la définition. Cela vous obtient une constante entière,FOO_SIZE
.Une
extern
variable est une déclaration (merci à sbi pour la correction) d'une variable qui est définie dans une autre unité de traduction. Cela signifie que le stockage de la variable est alloué dans un autre fichier.Disons que vous avez deux
.c
fichierstest1.c
ettest2.c
. Si vous définissez une variable globaleint test1_var;
danstest1.c
et que vous souhaitez accéder à cette variable,test2.c
vous devez l'utiliserextern int test1_var;
danstest2.c
.Échantillon complet:
la source
extern int test1_var;
enint test1_var;
, l'éditeur de liens (gcc 5.4.0) passe toujours. Alors, est-ceextern
vraiment nécessaire dans ce cas?extern
est une extension courante qui fonctionne souvent - et fonctionne spécifiquement avec GCC (mais GCC est loin d'être le seul compilateur qui le prend en charge; il est répandu sur les systèmes Unix). Vous pouvez chercher « J.5.11 » ou la section « Pas si bon chemin » dans ma réponse (je sais - il est long) et le texte près qui explique (ou tente de le faire).Extern est le mot-clé que vous utilisez pour déclarer que la variable elle-même réside dans une autre unité de traduction.
Vous pouvez donc décider d'utiliser une variable dans une unité de traduction, puis y accéder à partir d'une autre, puis dans la seconde, vous la déclarez externe et le symbole sera résolu par l'éditeur de liens.
Si vous ne le déclarez pas comme externe, vous obtiendrez 2 variables nommées de la même façon mais pas liées du tout, et une erreur de plusieurs définitions de la variable.
la source
J'aime penser à une variable externe comme une promesse que vous faites au compilateur.
Lorsqu'il rencontre un externe, le compilateur ne peut découvrir que son type, pas où il "vit", il ne peut donc pas résoudre la référence.
Vous lui dites: "Faites-moi confiance. Au moment du lien, cette référence sera résoluble."
la source
extern indique au compilateur de vous faire confiance que la mémoire de cette variable est déclarée ailleurs, il n'essaie donc pas d'allouer / vérifier la mémoire.
Par conséquent, vous pouvez compiler un fichier qui fait référence à un externe, mais vous ne pouvez pas lier si cette mémoire n'est pas déclarée quelque part.
Utile pour les variables globales et les bibliothèques, mais dangereux car l'éditeur de liens ne tape pas check.
la source
L'ajout d'un
extern
transforme une définition de variable en une déclaration de variable . Voir ce fil pour savoir quelle est la différence entre une déclaration et une définition.la source
int foo
etextern int foo
(portée du fichier)? Les deux sont une déclaration, n'est-ce pas?La déclaration n'allouera pas de mémoire (la variable doit être définie pour l'allocation de mémoire) mais la définition le sera. Ceci est juste une autre vue simple sur le mot-clé extern car les autres réponses sont vraiment excellentes.
la source
L'interprétation correcte de extern est que vous dites quelque chose au compilateur. Vous dites au compilateur que, bien qu'elle ne soit pas présente actuellement, la variable déclarée sera en quelque sorte trouvée par l'éditeur de liens (généralement dans un autre objet (fichier)). L'éditeur de liens sera alors le chanceux de tout trouver et de le rassembler, que vous ayez eu des déclarations externes ou non.
la source
En C, une variable à l'intérieur d'un fichier, par exemple example.c, a une portée locale. Le compilateur s'attend à ce que la variable ait sa définition dans le même fichier example.c et quand elle ne le trouve pas, elle lèverait une erreur. Une fonction en revanche a une portée globale par défaut. Ainsi, vous n'avez pas à mentionner explicitement au compilateur "look mec ... vous pourriez trouver la définition de cette fonction ici". Pour une fonction comprenant le fichier qui contient sa déclaration suffit (le fichier que vous appelez en fait un fichier d'en-tête). Par exemple, considérez les 2 fichiers suivants:
example.c
exemple1.c
Maintenant, lorsque vous compilez les deux fichiers ensemble, à l'aide des commandes suivantes:
étape 1) cc -o ex exemple.c exemple1.c étape 2) ./ ex
Vous obtenez la sortie suivante: La valeur de a est <5>
la source
Implémentation de GCC ELF Linux
D'autres réponses ont couvert le côté de l'utilisation de la langue, alors regardons maintenant comment il est implémenté dans cette implémentation.
principal c
Compiler et décompiler:
La sortie contient:
Le chapitre "Table des symboles " de la spécification ELF de mise à jour ABI System V explique:
qui est fondamentalement le comportement que la norme C donne aux
extern
variables.Désormais, c'est le lieur de faire le programme final, mais les
extern
informations ont déjà été extraites du code source dans le fichier objet.Testé sur GCC 4.8.
Variables en ligne C ++ 17
En C ++ 17, vous souhaiterez peut-être utiliser des variables en ligne au lieu de variables externes, car elles sont simples à utiliser (peuvent être définies une seule fois sur l'en-tête) et plus puissantes (prennent en charge constexpr). Voir: Que signifie «const statique» en C et C ++?
la source
readelf
ounm
puisse être utile, vous n'avez pas expliqué les principes fondamentaux de l'utilisationextern
, ni terminé le premier programme avec la définition réelle. Votre code n'utilise même pasnotExtern
. Il y a aussi un problème de nomenclature: bien quenotExtern
défini ici plutôt que déclaré avecextern
, il s'agit d'une variable externe à laquelle d'autres fichiers source pourraient accéder si ces unités de traduction contenaient une déclaration appropriée (ce qui serait nécessaireextern int notExtern;
!).notExtern
était moche, l'a corrigé. À propos de la nomenclature, faites-moi savoir si vous avez un meilleur nom. Bien sûr, ce ne serait pas un bon nom pour un programme réel, mais je pense que cela correspond bien au rôle didactique ici.global_def
de la variable définie ici etextern_ref
de la variable définie dans un autre module? Auraient-ils une symétrie suffisamment claire? Vous vous retrouvez toujours avecint extern_ref = 57;
ou quelque chose comme ça dans le fichier où il est défini, donc le nom n'est pas tout à fait idéal, mais dans le contexte du fichier source unique, c'est un choix raisonnable. Avoirextern int global_def;
dans un en-tête n'est pas autant un problème, il me semble. Tout dépend de vous, bien sûr.Le mot clé extern est utilisé avec la variable pour son identification en tant que variable globale.
la source
extern
permet à un module de votre programme d'accéder à une variable ou fonction globale déclarée dans un autre module de votre programme. Vous avez généralement des variables externes déclarées dans les fichiers d'en-tête.Si vous ne voulez pas qu'un programme accède à vos variables ou fonctions, vous utilisez
static
qui indique au compilateur que cette variable ou fonction ne peut pas être utilisée en dehors de ce module.la source
extern
signifie simplement qu'une variable est définie ailleurs (par exemple, dans un autre fichier).la source
Tout d'abord, le
extern
mot-clé n'est pas utilisé pour définir une variable; il est plutôt utilisé pour déclarer une variable. Je peux dire queextern
c'est une classe de stockage, pas un type de données.extern
est utilisé pour informer les autres fichiers C ou composants externes que cette variable est déjà définie quelque part. Exemple: si vous construisez une bibliothèque, pas besoin de définir obligatoirement une variable globale quelque part dans la bibliothèque elle-même. La bibliothèque sera compilée directement, mais lors de la liaison du fichier, elle vérifie la définition.la source
extern
est utilisé pour qu'unfirst.c
fichier puisse avoir un accès complet à un paramètre global dans un autresecond.c
fichier.Le
extern
peut être déclaré dans lefirst.c
fichier ou dans l'un des fichiers d'en-têtefirst.c
inclus.la source
extern
déclaration doit être dans un en-tête, pas dansfirst.c
, de sorte que si le type change, la déclaration changera aussi. En outre, l'en-tête qui déclare la variable doit être inclus parsecond.c
pour garantir la cohérence de la définition avec la déclaration. La déclaration dans l'en-tête est la colle qui tient tout cela ensemble; il permet de compiler les fichiers séparément mais garantit qu'ils ont une vue cohérente du type de la variable globale.Avec xc8, vous devez faire attention à déclarer une variable du même type dans chaque fichier car vous pourriez, par erreur, déclarer quelque chose d'un
int
dans un fichier et unchar
mot à dire dans un autre. Cela pourrait entraîner une corruption des variables.Ce problème a été élégamment résolu dans un forum sur les puces électroniques il y a une quinzaine d'années / * Voir "http: www.htsoft.com" / / "forum / all / showflat.php / Cat / 0 / Number / 18766 / an / 0 / page / 0 # 18766 "
Mais ce lien ne semble plus fonctionner ...
Je vais donc essayer de l'expliquer rapidement; créer un fichier appelé global.h.
Dans ce document, déclarez ce qui suit
Maintenant dans le fichier main.c
Cela signifie que dans main.c, la variable sera déclarée comme un
unsigned char
.Désormais, dans d'autres fichiers, il suffit de déclarer global.h comme fichier externe pour ce fichier .
Mais il sera correctement déclaré en tant que
unsigned char
.L'ancien message du forum expliquait probablement cela un peu plus clairement. Mais c'est un réel potentiel
gotcha
lorsque vous utilisez un compilateur qui vous permet de déclarer une variable dans un fichier, puis de la déclarer externe comme un type différent dans un autre. Les problèmes qui y sont associés sont que si vous dites déclaré testing_mode en tant qu'int dans un autre fichier, il penserait qu'il s'agissait d'une var 16 bits et écraserait une autre partie de ram, corrompant potentiellement une autre variable. Difficile à déboguer!la source
Une solution très courte que j'utilise pour permettre à un fichier d'en-tête de contenir la référence externe ou l'implémentation réelle d'un objet. Le fichier qui contient réellement l'objet fait juste
#define GLOBAL_FOO_IMPLEMENTATION
. Ensuite, lorsque j'ajoute un nouvel objet à ce fichier, il apparaît également dans ce fichier sans que je doive copier et coller la définition.J'utilise ce modèle sur plusieurs fichiers. Donc, afin de garder les choses aussi autonomes que possible, je réutilise simplement la macro GLOBAL unique dans chaque en-tête. Mon en-tête ressemble à ceci:
la source