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