Comment puis-je utiliser extern pour partager des variables entre les fichiers source?

je sais que les variables globales en C ont parfois le mot-clé extern . Qu'est-ce qu'une variable extern ? Qu'est-ce que la déclaration? Quelle est sa portée?

Ceci est lié au partage des variables entre les fichiers sources, mais comment cela fonctionne-t-il précisément? Où dois-je utiliser extern ?

831
demandé sur Lundin 2009-09-16 18:08:40

15 réponses

utiliser extern n'est pertinent que lorsque le programme que vous construisez se compose de plusieurs fichiers sources reliés entre eux, où certains des les variables définies, par exemple, dans le fichier source file1.c doivent être référencé dans d'autres fichiers source, tels que file2.c .

il est important de comprendre la différence entre définir a variable et déclarant a variable :

  • une variable est déclarée lorsque le compilateur est informé qu'une la variable existe (et c'est son type); elle n'attribue pas les stockage de la variable à ce point.
  • une variable est défini lorsque le compilateur attribue le stockage pour variable.

vous pouvez déclarer une variable plusieurs fois (bien qu'une fois est suffisant); vous ne pouvez définir qu'une fois dans une étendue donnée. Une définition de variable est aussi une déclaration, mais pas toutes les variables les déclarations sont des définitions.

meilleure façon de déclarer et de définir les variables globales

la façon propre et fiable de déclarer et de définir des variables globales est d'utiliser un fichier d'en-tête contenant une extern déclaration de la variable.

L'en-tête est inclus dans le fichier source qui définit la variable et par tous les fichiers sources qui font référence à la variable. Pour chaque programme, un fichier source (et un seul fichier source) définit l' variable. De même, un fichier d'en-tête (et un seul fichier d'en-tête), de déclarer le variable. Le fichier d'en-tête est crucial; il permet la vérification croisée entre les unités de traduction indépendantes (think source files) et assure 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 :

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 <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, ainsi les déclarations de fonction ont glissée dans l'. C99 et C11 exigent toutes deux que les fonctions soient déclarées ou définies avant qu'elles sont utilisés (alors que C90 ne l'a pas fait, 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 correspondre au extern devant la variable les déclarations dans les en-têtes. Beaucoup de gens préfèrent ne pas utiliser extern devant fonction déclarations; le compilateur s'en fiche - et en fin de Compte, moi non plus aussi longtemps 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 le tour des objectifs du millénaire pour le. 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  = -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}

lignes directrices

règles à enfreindre par les experts seulement, et seulement avec raison:

  • Un fichier d'en-tête ne contient que extern les 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 - Un seul point de vérité).
  • un fichier source ne contient jamais extern déclarations de variables - les fichiers source comprennent 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, l'initialisation de trop. (Bien qu'il n'est pas nécessaire de initialiser explicitement à zéro, il ne fait pas de mal et peut faire du bien, car il peut y avoir seulement une initialisé définition d'un particulier variable globale dans un programme).
  • le fichier source qui définit la variable inclut également l'en-tête à s'assurer que la définition et la déclaration sont conformes.
  • une fonction ne devrait jamais avoir besoin de déclarer une variable en utilisant extern .
  • éviter les variables globales dans la mesure du possible, utilisez plutôt des fonctions.

le code source et le texte de cette réponse sont disponibles dans mon OSQ (Débordement de Pile Questions) dépôt sur GitHub dans le src / so-0143-3204 sous-répertoire.

si vous n'êtes pas un programmeur expérimenté, vous pouvez (et peut-être devrait) arrêter de lire ici.

pas très bonne façon de définir les variables globales

avec certains (en effet, beaucoup) compilateurs C, vous pouvez vous en tirer avec appelé une "commune" définition d'une variable. "Common", ici, se réfère à une technique utilisée dans Fortran pour le partage variables entre les fichiers source, en utilisant un bloc commun (éventuellement nommé). Ce qui se passe ici est que chacun d'un certain nombre de fichiers fournit un provisoire définition de la variable. Dans la mesure où un seul fichier fournit une définition initialisée, ensuite, les différents fichiers finissent par partager une définition unique commune de la variable:

file10.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void inc(void) { i++; }

file11.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void dec(void) { i--; }

file12.c

#include "prog2.h"
#include <stdio.h>

int i = 9;   /* Do not do this in portable code */

void put(void) { printf("i = %d\n", i); }

Cette technique n'est pas conforme à la lettre de la norme et de la "une règle de définition" - il est officiellement comportement non défini:

J. 2 comportement non défini

un identifiant avec lien externe est utilisé, mais dans le programme il il n'existe pas exactement une définition externe pour l'identifiant, ou l'identificateur n'est pas utilisé et il existe plusieurs définitions pour l'identificateur (6.9).

§ 6.9 définitions externes¶5

Un externes définition de est une déclaration externe qui est aussi un définition d'une fonction (autre qu'une ligne de définition) ou un objet. Si un identifiant déclaré avec un lien externe est utilisé dans un l'expression (autre que dans le cadre de l'opérande d'une sizeof ou _Alignof opérateur dont le résultat est une constante entière), quelque part dans l'ensemble du programme il doit y avoir exactement une définition externe pour l'identificateur; sinon, il n'y aura pas plus de un. 161)

161) ainsi, si un identifiant déclaré Avec lien externe n'est pas utilisé dans une expression, il n'est pas besoin de définition externes pour il.

toutefois, la norme C l'indique également dans l'annexe informative J comme étant l'une des normes suivantes: le commun extensions .

J. 5.11 définitions externes multiples

Il peut y avoir plus d'une définition externe de l'identificateur de un objet, avec ou sans l'utilisation explicite du mot-clé extern; si les définitions diffèrent, ou plus d'une est initialisée, le le comportement est indéfini (6.9.2).

parce que cette technique n'est pas toujours soutenu, il est préférable d'éviter en l'utilisant, , surtout si votre code doit être portable . En utilisant cette technique, vous pouvez également finir avec le type non intentionnel beaucoup les jeux de mots. Si l'un des fichiers déclare i comme double au lieu de int , Les linkers non sécuritaires de type C ne détecteraient probablement pas l'inadéquation. Si vous êtes sur une machine avec 64 bits int et double , vous n'auriez même pas obtenez un avertissement; sur une machine 32 bits int et 64-bit double , vous probablement un avertissement sur les différentes dimensions de l'éditeur de liens serait utiliser la plus grande taille, exactement comme un programme Fortran prendrait la plus grande taille des 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 <stdio.h>

int main(void)
{
    inc();
    put();
    dec();
    put();
    dec();
    put();
}
  • prog2 utilisations prog2.c , file10.c , file11.c , file12.c , prog2.h .

avertissement

tel que noté dans les commentaires ici, et tel qu'indiqué dans ma réponse à une question , en utilisant plusieurs les définitions d'une variable globale conduisent à un comportement non défini (J. 2; § 6.9), ce qui est la façon de la norme de dire "tout peut arriver". L'un des les choses qui peuvent arriver, c'est que le programme se comporte comme vous expect; et J. 5.11 dit, environ, " vous pourriez être chanceux plus souvent que vous méritez". Mais un programme qui repose sur de multiples définitions d'une variable externe - avec ou sans le mot clé explicite "externe" programme conforme et pas garanti de travailler partout. D'une manière équivalente: il contient un bug qui peut ou non apparaître.

violation des lignes directrices

il y a, bien sûr, de nombreuses façons dont ces lignes directrices peuvent être enfreintes. Parfois, il peut être une bonne raison pour casser les lignes directrices, mais de telles occasions sont extrêmement inhabituel.

faulty_header.h

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

Note 1: si l'en-tête définit la variable sans le mot-clé extern , puis chaque fichier qui inclut l'en-tête crée une définition provisoire de la variable. Comme nous l'avons déjà mentionné, cela fonctionne souvent, mais la norme n'est pas garantie que cela fonctionnera.

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 seulement un fichier source dans un programme donné peut utiliser l'en-tête. Puisque les en-têtes sont principalement pour le partage de l'information, c'est un peu idiot pour en créer un qui ne peut être utilisée qu'une fois.

seldom_correct.h

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

Note 3: header définit une variable statique (avec ou sans initialisation), puis chaque fichier source finit avec son propre privé la version de la "global" de la variable.

Si la variable est en fait un ensemble complexe, par exemple, cela peut conduire jusqu'à la duplication extrême du code. Elle peut, très occasionnellement, être une façon raisonnable d'obtenir un certain effet, mais c'est très inhabituel.


sommaire

utilisez l'en-tête technique que j'ai montré en premier. Il fonctionne de manière fiable et partout. Remarque, en particulier, que l'en-tête déclarant la global_variable est inclus dans chaque fichier qui l'utilise - y compris celle qui la définit. Cela garantit que tout est cohérent.

des préoccupations similaires se posent en ce qui concerne la déclaration et la définition des fonctions - des règles analogues s'appliquent. Mais la question était sur les variables en particulier, donc j'ai gardé le réponse aux variables seulement.

fin de l'Original de la Réponse

si vous n'êtes pas un programmeur expérimenté de C, vous devriez probablement arrêter de lire ici.


Ajout Majeur Tardif

Éviter La Duplication Des Codes

une préoccupation qui est parfois (et légitimement) soulevée au sujet de la "déclarations dans les en-têtes, définitions dans source " mécanisme décrit voici qu'il y a deux fichiers à garder synchronisés - l'en-tête et de la source. Il est généralement suivi d'une observation selon laquelle macro peut être utilisé de sorte que l'en-tête sert double fonction-normalement déclarer les variables, mais quand une macro est définie avant la l'en-tête est inclus, il définit les variables à la place.

une Autre préoccupation peut être que les variables doivent être définies dans chacun des un certain nombre de 'programmes'. Ce est normalement une fausse inquiétude; vous peut simplement introduire un fichier source C pour définir les variables et le lien le fichier objet produit avec chacun des programmes.

un schéma typique fonctionne comme ceci, en utilisant la variable globale originale illustré 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 montré est qu'il ne prévoit pas l'initialisation de la variable globale. Avec C99 ou C11 et argument variable listes pour les macros, vous pouvez définir une macro pour supporter l'initialisation aussi. (Avec C89 et aucun support pour les listes d'arguments variables dans les macros, il n'y a pas façon facile de manipuler arbitrairement les initialisateurs 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 });

"15192620920 de" Renverser le contenu de #if et #else blocks, correction de 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 pour la structure oddball n'est pas ce que vous auriez normalement Ecrivez, mais cela illustre le point. Le premier argument au second l'invocation de INITIALIZER est { 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, initialiseurs qui ont besoin de les guillemets 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 .

L'En-Tête Des Gardes

tout en-tête doit être protégé contre la réinclusion, de sorte que ce type définitions (enum, struct ou union types, ou typedefs en général) pas causer des problèmes. La technique standard est d'envelopper le corps du en-tête de dans un en-tête de la garde tels 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 comprend file3b.h pour une définition de type qui n'est pas montré, et file1b.c doit utiliser à la fois en-tête file4b.h et file3b.h , puis vous avez des problèmes plus délicats à résoudre. Clairement, vous pourriez réviser la liste d'en-tête à inclure juste file4b.h . Cependant, vous pourriez ne pas être conscient de dépendances internes et le code devrait, idéalement, continuer à travailler.

plus loin, il commence à devenir délicat parce que vous pourriez inclure file4b.h avant d'inclure file3b.h pour générer les définitions, mais la normale des en-têtes sur file3b.h empêcheraient l'en-tête d'être réincarné.

Donc, vous devez inclure le corps de file3b.h au plus une fois pour déclaration, et plus d'une fois pour les définitions, mais vous pourriez avoir besoin à la fois dans une seule unité de traduction (TU - une combinaison d'un fichier source et les en-têtes qu'il utilise).

inclusion Multiple avec définitions variables

cependant, il peut être fait sous une contrainte pas trop déraisonnable. Introduisons un nouvel ensemble de noms de fichiers:

  • external.h pour les macro définitions 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 comprennent directement l'en-tête file2c.h plusieurs fois, mais c'est la façon la plus simple de montrer que l' mécanisme fonctionne. Cela signifie que si l'en-tête a été indirectement inclus deux fois, il serait aussi fort.

Les restrictions pour ce travail sont:

  1. l'en-tête définissant ou déclarant les variables globales ne peut pas lui-même définir les types.
  2. immédiatement avant d'inclure un en-tête qui devrait définir les variables, vous définissez la macro DEFINE_VARIABLES.
  3. l'en-tête définissant ou déclarant les variables a un contenu stylisé.

externe.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is invoked, it redefines the macros EXTERN, INITIALIZE
** based on whether macro DEFINE_VARIABLES is currently defined.
*/
#undef EXTERN
#undef INITIALIZE

#ifdef DEFINE_VARIABLES
#define EXTERN              /* nothing */
#define INITIALIZE(...)     = __VA_ARGS__
#else
#define EXTERN              extern
#define INITIALIZE(...)     /* nothing */
#endif /* DEFINE_VARIABLES */

file1c.h

#ifndef FILE1C_H_INCLUDED
#define FILE1C_H_INCLUDED

struct oddball
{
    int a;
    int b;
};

extern void use_them(void);
extern int increment(void);
extern int oddball_value(void);

#endif /* FILE1C_H_INCLUDED */

file2c.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2C_H_DEFINITIONS)
#undef FILE2C_H_INCLUDED
#endif

#ifndef FILE2C_H_INCLUDED
#define FILE2C_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file1c.h"     /* Type definition for struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE2C_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });

#endif /* !DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */

#endif /* FILE2C_H_INCLUDED */

file3c.c

#define DEFINE_VARIABLES
#include "file2c.h"  /* Variables now defined and initialized */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file4c.c

#include "file2c.h"
#include <stdio.h>

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

file5c.c

#include "file2c.h"     /* Declare variables */

#define DEFINE_VARIABLES
#include "file2c.h"  /* Variables now defined and initialized */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file6c.c

#define DEFINE_VARIABLES
#include "file2c.h"     /* Variables now defined and initialized */

#include "file2c.h"     /* Declare variables */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

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 système permet d'éviter la plupart des problèmes. Vous n'exécutez un problème si un l'en-tête qui définit les variables (comme file2c.h ) est inclus par un autre en-tête (dites file7c.h ) qui définit les variables. Il n'y a pas un moyen facile de contourner autre que "ne fais pas cela".

vous pouvez partiellement contourner le 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 */

La question devient " devrait l'en-tête #undef DEFINE_VARIABLES ?' Si vous omettez cela de l'en-tête et envelopper toute invocation de définition avec #define et #undef :

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

dans le code source (de sorte que les en-têtes ne modifient jamais la valeur de DEFINE_VARIABLES ), alors vous devez être propre. C'est juste une nuisance pour faut pas oublier d'écrire le la ligne supplémentaire. Une autre solution pourrait être:

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

externdef.h

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

#if defined(HEADER_DEFINING_VARIABLES)
#define DEFINE_VARIABLES
#include HEADER_DEFINING_VARIABLES
#undef DEFINE_VARIABLES
#undef HEADER_DEFINING_VARIABLES
#endif /* HEADER_DEFINING_VARIABLES */

cela devient un tad alambiqué, mais semble être sûr (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 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 suivants les fichiers complètent la source pour 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, les problèmes sont relativement peu probable de se produire dans la pratique, surtout si vous prenez le conseil standard à

Evite global variables


cette exposition manque-t-elle quelque chose?

Confession : le schéma "éviter de dupliquer le code" décrit ici était développé parce que la question affecte un code sur lequel je travaille (mais ne possède pas), et est un souci de nègre avec le le schéma décrit dans la première partie de réponse. Cependant, le système d'origine vous laisse avec seulement deux endroits à modifier pour conserver les définitions de variables et les déclarations synchronisé, ce qui est un grand pas en avant par rapport à avoir variable exernale déclarations dispersées dans la base de code (ce qui importe vraiment quand il y a des milliers de dossiers au total). Toutefois, le code de la fichiers portant les noms fileNc.[ch] (plus external.h et externdef.h ) montre qu'il peut être mis en oeuvre. 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 le fichier d'en-tête.

NB ce sont des programmes jouets avec à peine assez de code pour les rendre légèrement intéressant. Il y a répétition dans les exemples qui pourrait être supprimé, mais n'est pas de simplifier l'explication pédagogique. (Par exemple: la différence entre prog5.c et prog8.c est nom de un des en-têtes qui sont inclus. Il serait possible de réorganiser le code de manière à ce que la fonction main() ne soit pas répétée, mais il masquerait plus que ce qu'il a révélé.)

1513
répondu Jonathan Leffler 2018-07-03 16:04:34

une variable extern est une déclaration (grâce à 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.

dites que vous avez deux .c - 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 le extern int test1_var; dans test2.c .

échantillon complet:

$ cat test1.c 
int test1_var = 5;
$ cat test2.c
#include <stdio.h>

extern int test1_var;

int main(void) {
    printf("test1_var = %d\n", test1_var);
    return 0;
}
$ gcc test1.c test2.c -o test
$ ./test
test1_var = 5
108
répondu Johannes Weiss 2017-02-18 20:49:36

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.

pour que vous puissiez décider d'utiliser une variable dans une unité de traduction et y accéder à partir d'une autre, puis dans la seconde vous la déclarez comme externe et le symbole sera résolu par le linker.

si vous ne le déclarez pas comme extern vous obtiendrez 2 variables nommées les mêmes mais pas liées du tout, et une erreur de définitions multiples de variable.

35
répondu Arkaitz Jimenez 2009-09-16 14:21:48

j'aime penser à une variable externe comme une promesse que vous faites au compilateur.

lorsqu'il rencontre un extern, le compilateur ne peut trouver que son type, et non pas où il "vit", de sorte qu'il ne peut pas résoudre la référence.

vous le dites, "croyez-moi. Au moment du lien, cette référence sera résoluble."

23
répondu Buggieboy 2009-09-16 14:50:53

extern dit au compilateur de vous faire confiance que la mémoire pour cette variable est déclarée ailleurs, donc il n'essaie pas d'allouer/vérifier la mémoire.

par conséquent, vous pouvez compiler un fichier qui a une 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 parce que le linker ne tape pas de vérification.

17
répondu BenB 2009-09-16 14:18:57

L'ajout d'une extern transforme une variable définition en une variable déclaration . Voir ce fil quelle est la différence entre une déclaration et une définition.

15
répondu sbi 2017-05-23 12:18:36

l'interprétation correcte d'extern est que vous dites quelque chose au compilateur. Vous dites au compilateur que, bien qu'elle ne soit pas présente en ce moment, la variable déclarée sera en quelque sorte trouvée par le linker (typiquement dans un autre objet (fichier)). Le linker sera alors le gars chanceux pour trouver tout et mettre en place, que vous avez eu quelques déclarations externes ou non.

11
répondu Alex Lockwood 2012-06-20 23:43:15

dans C une variable à l'intérieur d'un fichier dire exemple.c a une portée locale. Le compilateur s'attend à ce que la variable ait sa définition dans le même exemple de fichier.c et lorsqu'il ne trouve pas le même , il renvoie une erreur.Une fonction par contre a par défaut une portée globale . Ainsi, vous n'avez pas à mentionner explicitement au compilateur "look mec...vous pouvez trouver la définition de cette fonction ici". Pour une fonction incluant le fichier qui contient sa déclaration est assez.(Le fichier que vous appelez en fait un fichier d'en-tête). Par exemple, considérez les 2 fichiers suivants:

exemple.c

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

exemple1.c

int a = 5;

Maintenant, lorsque vous compilez les deux fichiers ensemble, en utilisant les commandes suivantes :

étape 1)cc -o ex exemple.c exemple1.C étape 2)./ ex 151950920"

vous obtenez la sortie suivante : la valeur de a est <5>

8
répondu Phoenix225 2012-08-20 10:45:58

mot clé externe est utilisé avec la variable pour son identification comme une variable globale.

il représente également que vous pouvez utiliser la variable déclarée en utilisant extern mot-clé dans n'importe quel fichier bien qu'il soit déclaré/défini dans un autre fichier.

7
répondu Anup 2012-08-20 10:19:51

extern permet à un module de votre programme pour accéder à une variable globale ou d'une fonction déclarée dans un autre module de votre programme. Vous avez habituellement 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.

5
répondu loganaayahee 2015-09-02 15:00:43

tout d'abord, le mot-clé extern n'est pas utilisé pour définir une variable; il est plutôt utilisé pour déclarer une variable. Je peux dire que extern est une classe de stockage, pas un type de données.

extern est utilisé pour indiquer à d'autres fichiers C ou à des composants externes que cette variable est déjà définie quelque part. Exemple: si vous construisez une bibliothèque, pas besoin de définir la variable globale obligatoirement quelque part dans la bibliothèque elle-même. La bibliothèque sera compilée directement, mais tout en reliant le fichier, il vérifie la définition.

4
répondu user1270846 2015-09-02 15:04:31

extern est utilisé pour qu'un fichier first.c puisse avoir un accès complet à un paramètre global dans un autre fichier second.c .

Le extern peut être déclaré dans la first.c fichier ou des fichiers d'en-tête first.c .

3
répondu shoham 2015-09-02 15:06:48

extern signifie simplement qu'une variable est définie ailleurs (p. ex. dans un autre fichier).

3
répondu Geremia 2018-05-05 06:34:42

avec xc8 vous devez être prudent sur la déclaration d'une variable comme le même type dans chaque fichier que vous pourriez, par erreur, déclarez quelque chose comme int dans un fichier et char dans un autre. Cela pourrait conduire à la corruption des variables.

ce problème a été élégamment résolu dans un forum de micropuces il y a environ 15 ans / * Voir "http:www.htsoft.com " / / "forum/toutes les/showflat.php/Chat/0/Nombre de/18766//0/page/0#18766"

mais ce lien ne semble plus fonctionner...

alors je vais rapidement essayer de l'expliquer; rendre un fichier appelé mondiale.H.

y déclarent ce qui suit

#ifdef MAIN_C
#define GLOBAL
 /* #warning COMPILING MAIN.C */
#else
#define GLOBAL extern
#endif
GLOBAL unsigned char testing_mode; // example var used in several C files

Maintenant dans le fichier principal.c

#define MAIN_C 1
#include "global.h"
#undef MAIN_C

cela signifie principalement.c la variable sera déclarée comme une unsigned char .

maintenant dans d'autres fichiers simplement en incluant mondiale.h faites-la déclarer comme un extern pour ce fichier .

extern unsigned char testing_mode;

Mais il sera correctement déclaré comme un unsigned char .

l'ancien post de forum a probablement expliqué cela un peu plus clairement. Mais c'est un réel potentiel gotcha lorsqu'on utilise un compilateur qui vous permet de déclarer une variable dans un fichier, et ensuite déclarer extern comme un type différent d'un autre. Les problèmes associés avec que si vous dites déclaré testing_mode comme un int dans un autre fichier il pense qu'il s'agit d'un var 16 bits et qu'il écrase une autre partie de la mémoire vive, corrompant potentiellement une autre variable. Difficile à déboguer!

0
répondu user50619 2018-10-09 12:16:32