Comment utiliser extern pour partager des variables entre les fichiers sources?

Je sais que les variables globales de C ont parfois le mot-clé extern . Qu’est-ce qu’une variable extern ? Quelle est la déclaration comme? Quelle est sa scope?

Ceci est lié au partage de variables entre les fichiers sources, mais comment cela fonctionne-t-il avec précision? Où est-ce que j’utilise extern ?

Utiliser extern est uniquement pertinent lorsque le programme que vous créez se compose de plusieurs fichiers sources 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 sources, 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 (même si une fois suffit); vous ne pouvez le définir qu’une fois dans un périmètre donné. Une définition de variable est également une déclaration, mais toutes les déclarations de variable ne sont pas des définitions.

Meilleur moyen 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 déclaration extern de la variable.

L’en-tête est inclus dans le fichier source unique qui définit la variable et par tous les fichiers sources faisant référence à 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 la vérification croisée entre les unités de traduction indépendantes (unités de traduction – fichiers sources) et assure la cohérence.

Bien qu’il existe d’autres moyens, cette méthode est simple et fiable. Il est démontré par file3.h , file1.c et file2.c :

fichier3.h

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

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

fichier2.c

 #include "file3.h" #include "prog1.h" #include  void use_it(void) { printf("Global variable: %d\n", global_variable++); } 

C’est le meilleur moyen 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 requièrent 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 l’ 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 fonctions; le compilateur ne se soucie pas – 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  int main(void) { use_it(); global_variable += 19; use_it(); printf("Increment: %d\n", increment()); return 0; } 
  • prog1 utilise prog1.c , file1.c , file2.c , file3.h et prog1.h .

Le fichier prog1.mk est un makefile pour prog1 uniquement. Il fonctionnera avec la plupart des versions de produits fabriqués depuis le tournant du millénaire. Il n’est pas spécifiquement lié à 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 = -Wssortingct-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 direcsortingces

Règles à briser par des experts uniquement, et pour une bonne raison:

  • Un fichier d’en-tête ne contient que des déclarations extern de variables – jamais static définitions de variables static ou non qualifiées.
  • Pour toute variable donnée, un seul fichier d’en-tête le déclare (SPOT – Single Point of Truth).
  • Un fichier source ne contient jamais de déclaration extern de variables – les fichiers source contiennent toujours l’en-tête (unique) qui les déclare.
  • Pour une variable donnée, exactement un fichier source définit la variable, de préférence l’initialisant également. (Bien qu’il ne soit pas nécessaire d’initialiser explicitement à zéro, il ne fait pas de mal 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 inclut é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 en utilisant 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 pourriez (et peut-être devriez-vous) arrêter de lire ici.

Pas si bon moyen de définir des variables globales

Avec certains compilateurs C (en fait, beaucoup), vous pouvez vous en sortir avec ce qu’on appelle une définition «commune» d’une variable. «Common», 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 des fichiers fournit une définition provisoire de la variable. Tant qu’aucun fichier ne fournit une définition initialisée, les différents fichiers partagent une définition unique commune de la variable:

fichier10.c

 #include "prog2.h" int i; /* Do not do this in portable code */ void inc(void) { i++; } 

fichier11.c

 #include "prog2.h" int i; /* Do not do this in portable code */ void dec(void) { i--; } 

fichier12.c

 #include "prog2.h" #include  int i = 9; /* Do not do this in portable code */ void put(void) { printf("i = %d\n", i); } 

Cette technique n’est pas conforme à la lettre du standard C et à la «règle de définition unique» – c’est un comportement officiellement indéfini:

J.2 Comportement non défini

Un identifiant avec lien 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 inline) ou un object. Si un identificateur déclaré avec un lien externe est utilisé dans une expression (autre que dans l’opérande d’un opérateur sizeof ou _Alignof dont le résultat est une constante entière), il doit exister une seule définition externe pour l’identifiant dans tout le programme; sinon, il n’y en aura pas plus d’un. 161)

161) Ainsi, si un identificateur déclaré avec un lien externe n’est pas utilisé dans une expression, il ne doit pas y avoir de définition externe.

Cependant, le standard C le répertorie également dans l’Annexe J informative comme l’une des extensions communes .

J.5.11 Plusieurs définitions externes

Il peut y avoir plus d’une définition externe pour l’identifiant d’un object, avec ou sans l’utilisation explicite du mot-clé extern; Si les définitions ne sont pas d’accord ou si plusieurs sont initialisées, le comportement n’est pas défini (6.9.2).

Comme cette technique n’est pas toujours prise en charge, il est préférable d’éviter de l’utiliser, surtout si votre code doit être portable . En utilisant cette technique, vous pouvez également vous retrouver avec des punitions de type non intentionnel. Si l’un des fichiers déclarait i tant que double au lieu de en tant int , les éditeurs de liens de type C ne détecteront probablement pas la non-concordance. Si vous êtes sur une machine avec int et double 64 bits, vous ne recevrez même pas d’avertissement; sur une machine avec int 32 bits et double 64 bits, vous obtiendrez probablement un avertissement sur les différentes tailles – l’éditeur de liens utilisera la plus grande taille, exactement comme un programme Fortran prendrait la plus grande taille de tous les blocs communs.


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  int main(void) { inc(); put(); dec(); put(); dec(); put(); } 
  • prog2 utilise prog2.c , file10.c , file11.c , file12.c , prog2.h .

Attention

Comme noté dans les commentaires ici, et comme indiqué dans ma réponse à une question similaire, l’utilisation de définitions multiples pour une variable globale conduit à un comportement indéfini (J.2; §6.9), qui est la manière de dire “tout peut arriver”. L’une des choses qui peut arriver est que le programme se comporte comme prévu; et J.5.11 dit, approximativement, “vous pourriez avoir de la chance plus souvent que vous ne le méritez”. Mais un programme qui s’appuie sur plusieurs définitions d’une variable externe – avec ou sans le mot clé explicite «extern» – n’est pas un programme ssortingctement conforme et ne garantit pas son fonctionnement partout. De manière équivalente: il contient un bug qui peut ou peut ne pas apparaître.

Violer les directives

Il existe bien sûr de nombreuses manières de briser 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

 int some_var; /* Do not do this in a header!!! */ 

Remarque 1: si l’en-tête définit la variable sans le mot-clé extern , chaque fichier contenant 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 son bon fonctionnement.

broken_header.h

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

Note 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. Les en-têtes étant 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

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

Note 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 «global».

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 judicieux d’atteindre certains effets, mais c’est très inhabituel.


Résumé

Utilisez la technique d’en-tête que j’ai montrée en premier. Il fonctionne de manière fiable et partout. Notez, en particulier, que l’en-tête déclarant la 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 lors de la déclaration et de la définition des fonctions – des règles analogues s’appliquent. Mais la question concernait spécifiquement les variables, alors j’ai 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.


Ajout majeur tardif

Éviter la duplication de code

Une préoccupation qui est parfois (et légitimement) soulevée à propos du mécanisme de «déclarations dans les en-têtes, définitions dans la source» décrite ici est qu’il y a deux fichiers à conserver 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 – en déclarant normalement les variables, mais lorsqu’une macro spécifique est définie avant l’inclusion de l’en-tête, elle définit les variables.

Une autre préoccupation peut être que les variables doivent être définies dans chacun des «principaux programmes». Ceci est normalement une fausse préoccupation; vous pouvez simplement introduire un fichier source C pour définir les variables et lier le fichier object produit avec chacun des programmes.

Un schéma typique fonctionne comme ceci, en utilisant la variable globale originale illustrée dans le file3.h :

fichier3a.h

 #ifdef DEFINE_VARIABLES #define EXTERN /* nothing */ #else #define EXTERN extern #endif /* DEFINE_VARIABLES */ EXTERN int global_variable; 

fichier1a.c

 #define DEFINE_VARIABLES #include "file3a.h" /* Variable defined - but not initialized */ #include "prog3.h" int increment(void) { return global_variable++; } 

fichier2a.c

 #include "file3a.h" #include "prog3.h" #include  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  int main(void) { use_it(); global_variable += 19; use_it(); printf("Increment: %d\n", increment()); return 0; } 
  • prog3 utilise prog3.c , file1a.c , file2a.c , file3a.h , prog3.h .

Initialisation des variables

Le problème avec ce schéma, comme indiqué, est qu’il ne fournit pas d’initialisation de la variable globale. Avec C99 ou C11 et les listes d’arguments variables pour les macros, vous pouvez définir une macro pour prendre en charge l’initialisation également. (Avec C89 et l’absence de prise en charge des listes d’arguments variables dans les macros, il n’y a pas de moyen simple de gérer des initialiseurs longs et arbitraires.)

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 des blocs #if et #else , corriger le bogue identifié par Denis Kniazhev

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

fichier2b.c

 #include "file3b.h" #include "prog4.h" #include  void use_them(void) { printf("Global variable: %d\n", global_variable++); oddball_struct.a += global_variable; oddball_struct.b -= global_variable / 2; } 

Clairement, le code pour la structure de l’oddball n’est pas ce que vous écrivez normalement, mais il illustre le point. Le premier argument de la deuxième invocation de INITIALIZER est { 41 et l’argument restant (au 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.

file3b.h entête 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  int main(void) { use_them(); global_variable += 19; use_them(); printf("Increment: %d\n", increment()); printf("Oddball: %d\n", oddball_value()); return 0; } 
  • prog4 utilise prog4.c , file1b.c , file2b.c , prog4.h , file3b.h .

Gardes d’en-tête

Tout en-tête doit être protégé contre la réinclusion, de sorte que les définitions de type (types enum, struct 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 garde 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 deux fois indirectement. Par exemple, si file4b.h inclut file3b.h pour une définition de type non affichée et que file1b.c doit utiliser à la fois les file4b.h et file3b.h , vous devez résoudre des problèmes plus complexes. De toute évidence, vous pouvez réviser la liste des en-têtes pour inclure simplement 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 délicat car vous pourriez inclure file4b.h avant d’inclure file3b.h pour générer les définitions, mais les gardes d’en-tête normales sur file3b.h empêcheraient l’en-tête d’être réintroduit.

Donc, vous devez inclure le corps de 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 (une combinaison d’un fichier source et des en-têtes) .

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 les 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 qui montre que vous pouvez déclarer puis définir les variables globales.
  • file6c.c qui montre que vous pouvez définir puis (tenter de) déclarer les variables globales.

Dans ces exemples, file5c.c et file6c.c incluent directement l’en-tête file2c.h plusieurs fois, mais c’est la manière la 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 ressortingctions pour que cela fonctionne sont les suivantes:

  1. L’en-tête définissant ou déclarant les variables globales ne peut définir lui-même aucun type.
  2. Juste avant d’inclure un en-tête qui devrait définir des variables, vous définissez la macro DEFINE_VARIABLES.
  3. L’en-tête définissant ou déclarant les variables a un contenu stylisé.

external.h

 /* ** This header must not contain header guards (like  must not). ** Each time it is invoked, it redefines the macros EXTERN, INITIALIZE ** based on whether macro DEFINE_VARIABLES is currently defined. */ #undef EXTERN #undef INITIALIZE #ifdef DEFINE_VARIABLES #define EXTERN /* nothing */ #define INITIALIZE(...) = __VA_ARGS__ #else #define EXTERN extern #define INITIALIZE(...) /* nothing */ #endif /* DEFINE_VARIABLES */ 

file1c.h

 #ifndef FILE1C_H_INCLUDED #define FILE1C_H_INCLUDED struct oddball { int a; int b; }; extern void use_them(void); extern int increment(void); extern int oddball_value(void); #endif /* FILE1C_H_INCLUDED */ 

file2c.h

 /* Standard prolog */ #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  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  int main(void) { use_them(); global_variable += 19; use_them(); printf("Increment: %d\n", increment()); printf("Oddball: %d\n", oddball_value()); return 0; } 
  • prog5 utilise prog5.c , file3c.c , file4c.c , file1c.h , file2c.h , external.h .
  • prog6 utilise prog5.c , file5c.c , file4c.c , file1c.h , file2c.h , external.h .
  • prog7 utilise prog5.c , file6c.c , file4c.c , file1c.h , file2c.h , external.h .

Ce schéma évite la plupart des problèmes. Vous rencontrez seulement un problème si un en-tête qui définit des variables (telles que file2c.h ) est inclus par un autre en-tête (par exemple, file7c.h ) qui définit les variables. Il n’y a pas d’autre moyen facile que de ne pas le faire.

Vous pouvez contourner partiellement le problème en révisant file2c.h dans file2d.h :

fichier2d.h

 /* Standard prolog */ #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 est le suivant: l’en-tête devrait-il inclure #undef DEFINE_VARIABLES ? Si vous omettez cela de l’en-tête et que vous enroulez toute invocation de définition avec #define et #undef :

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

dans le code source (donc les en-têtes ne modifient jamais la valeur de DEFINE_VARIABLES ), alors vous devriez être propre. C’est juste une nuisance de devoir se rappeler d’écrire la ligne supplémentaire. Une alternative pourrait être:

 #define HEADER_DEFINING_VARIABLES "file2c.h" #include "externdef.h" 

externdef.h

 /* ** This header must not contain header guards (like  must not). ** Each time it is included, the macro HEADER_DEFINING_VARIABLES should ** be defined with the name (in quotes - or possibly angle brackets) of ** the header to be included that defines variables when the macro ** DEFINE_VARIABLES is defined. See also: external.h (which uses ** DEFINE_VARIABLES and defines macros EXTERN and INITIALIZE ** appropriately). ** ** #define HEADER_DEFINING_VARIABLES "file2c.h" ** #include "externdef.h" */ #if defined(HEADER_DEFINING_VARIABLES) #define DEFINE_VARIABLES #include HEADER_DEFINING_VARIABLES #undef DEFINE_VARIABLES #undef HEADER_DEFINING_VARIABLES #endif /* HEADER_DEFINING_VARIABLES */ 

Cela devient un peu compliqué, mais semble être sécurisé (en utilisant le file2d.h , sans #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 prolog */ #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  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  void use_them(void) { printf("Global variable: %d\n", global_variable++); oddball_struct.a += global_variable; oddball_struct.b -= global_variable / 2; } 
  • prog8 utilise prog8.c , file7c.c , file9c.c .
  • prog9 utilise prog8.c , file8c.c , file9c.c .

Cependant, les problèmes sont relativement improbables dans la pratique, surtout si vous prenez les conseils habituels pour

Eviter les variables globales


Est-ce que cette exposition manque quelque chose?

Confession : le schéma «éviter le code dupliqué» décrit ici a été développé car le problème affecte certains codes sur lesquels je travaille (mais ne les possède pas) et constitue une préoccupation dérangeante du 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 que les définitions de variables et les déclarations restnt synchronisées, ce qui représente un grand pas en avant en ce qui concerne les déclarations de variables externes dispersées dans la base de code (ce qui compte vraiment lorsqu’il y a des milliers de fichiers) . Cependant, le code dans les fichiers portant les noms fileNc.[ch] (plus external.h et externdef.h ) indique que cela peut fonctionner. Clairement, 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 une variable définissant et déclarant un fichier d’en-tête.

NB: Ce sont des programmes de jouets avec à peine assez de code pour les rendre légèrement intéressants. Il y a des répétitions dans les exemples qui pourraient être supprimées, mais cela ne simplifie pas 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 pour que la fonction main() ne soit pas répétée, mais elle cacherait plus qu’il a révélé.)

Une variable extern est une déclaration (grâce à sbi pour la correction) d’une variable 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 fichiers test1.c et test2.c . Si vous définissez une variable globale int test1_var; dans test1.c et vous souhaitez accéder à cette variable dans test2.c vous devez utiliser extern int test1_var; dans test2.c .

Échantillon complet:

 $ cat test1.c int test1_var = 5; $ cat test2.c #include  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 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 depuis une autre, puis dans la seconde, vous la déclarez comme externe et le symbole sera résolu par l’éditeur de liens.

Si vous ne le déclarez pas en tant que extern, vous obtiendrez 2 variables nommées de la même façon mais pas du tout liées, et une erreur de définitions multiples de la variable.

J’aime penser à une variable externe comme une promesse que vous faites au compilateur.

En rencontrant un extern, le compilateur ne peut trouver que son type, pas où il “vit”, donc il ne peut pas résoudre la référence.

Vous dites: “Faites-moi confiance. Au moment du lien, cette référence sera résolue.”

extern indique au compilateur de vous faire croire que la mémoire de cette variable est déclarée ailleurs, elle n’essaie donc pas d’allouer / de vérifier la mémoire.

Par conséquent, vous pouvez comstackr un fichier qui fait référence à un extern, 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.

L’ajout d’un extern transforme une définition de variable en déclaration de variable. Voir ce fil de discussion sur la différence entre une déclaration et une définition.

L’interprétation correcte de extern est que vous racontez quelque chose au compilateur. Vous dites au compilateur que, bien que n’étant pas présent actuellement, la variable déclarée sera en quelque sorte trouvée par l’éditeur de liens (généralement dans un autre object (fichier)). L’éditeur de liens sera alors le chanceux à trouver tout et à le rassembler, que vous ayez ou non des déclarations externes.

Dans C, une variable à l’intérieur d’un fichier indique que example.c a une scope locale. Le compilateur s’attend à ce que la variable ait sa définition dans le même fichier example.c et quand elle ne trouve pas la même chose, cela provoquerait une erreur. En revanche, une fonction a une scope globale par défaut. Thus you do not have to explicitly mention to the comstackr “look dude…you might find the definition of this function here”. For a function including the file which contains its declaration is enough.(The file which you actually call a header file). For example consider the following 2 files :
example.c

 #include extern int a; main(){ printf("The value of a is <%d>\n",a); } 

example1.c

 int a = 5; 

Now when you comstack the two files together, using the following commands :

step 1)cc -o ex example.c example1.c step 2)./ex

You get the following output : The value of a is <5>

extern keyword is used with the variable for its identification as a global variable.

It also represents that you can use the variable declared using extern keyword in any file though it is declared/defined in other file.

GCC ELF Linux implementation

main.c :

 #include  int not_extern_int = 1; extern int extern_int; void main() { printf("%d\n", not_extern_int); printf("%d\n", extern_int); } 

Comstack and decomstack:

 gcc -c main.c readelf -s main.o 

Output contains:

 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 

The System V ABI Update ELF spec “Symbol Table” chapter explains:

SHN_UNDEF This section table index means the symbol is undefined. When the link editor combines this object file with another that defines the indicated symbol, this file’s references to the symbol will be linked to the actual definition.

which is basically the behavior the C standard gives to extern variables.

From now on, it is the job of the linker to make the final program, but the extern information has already been extracted from the source code into the object file.

Tested on GCC 4.8.

First off, the extern keyword is not used for defining a variable; rather it is used for declaring a variable. I can say extern is a storage class, not a data type.

extern is used to let other C files or external components know this variable is already defined somewhere. Example: if you are building a library, no need to define global variable mandatorily somewhere in library itself. The library will be comstackd directly, but while linking the file, it checks for the definition.

extern allows one module of your program to access a global variable or function declared in another module of your program. You usually have extern variables declared in header files.

If you don’t want a program to access your variables or functions, you use static which tells the comstackr that this variable or function cannot be used outside of this module.

extern is used so one first.c file can have full access to a global parameter in another second.c file.

The extern can be declared in the first.c file or in any of the header files first.c includes.

extern simply means a variable is defined elsewhere (eg, in another file).