Question Comment utiliser extern pour partager des variables entre des fichiers source?


Je sais que les variables globales en C ont parfois la extern mot-clé. Qu'est-ce qu'un extern variable? À quoi ressemble la déclaration? Quelle est sa portée?

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


810
2017-09-16 14:08


origine


Réponses:


En utilisant extern est seulement pertinent lorsque le programme que vous construisez se compose de plusieurs fichiers source reliés entre eux, où certains des variables définies, par exemple, dans le fichier source file1.c besoin d'être référencé 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é lorsque le compilateur est informé qu'un la variable existe (et c'est son type); il n'alloue pas le stockage pour la variable à ce moment-là.
  • Une variable est défini lorsque le compilateur alloue le stockage pour la variable.

Vous pouvez déclarer une variable plusieurs fois (bien qu'une fois soit suffisante); vous ne pouvez le définir qu'une fois dans une portée donnée. Une définition de variable est également une déclaration, mais pas toute variable les déclarations sont des définitions.

Le meilleur moyen de déclarer et de définir des 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 pour contenir un 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 le variable. De même, un fichier d'en-tête (et un seul fichier d'en-tête) doit déclarer variable. Le fichier d'en-tête est crucial. il permet la vérification croisée entre TU indépendants (unités de traduction - pensez à des fichiers source) et 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 */

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

file2.c

#include "file3.h"
#include "prog1.h"
#include <stdio.h>

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 pour prog1:

Les programmes complets montrés utilisent des fonctions, donc les déclarations de fonction ont glissé dedans. Les deux C99 et C11 exigent des fonctions à être déclarées ou définies avant qu'ils sont utilisés (alors que C90 n'a pas, 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 à la extern devant la variable déclarations dans les en-têtes. Beaucoup de gens préfèrent ne pas utiliser extern devant la fonction déclarations; le compilateur ne se soucie pas - et finalement, je ne le fais pas 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 les usages prog1.c, file1.c, file2.c, file3.h et prog1.h.

Le fichier prog1.mk est un makefile pour prog1 seulement. Cela fonctionnera avec la plupart des versions de make produit depuis 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

Règles à être brisées par des experts seulement, et seulement avec une bonne raison:

  • Un fichier d'en-tête contient uniquement extern déclarations de variables - jamais static ou des définitions de variables non qualifiées.
  • Pour une variable donnée, un seul fichier en-tête le déclare (SPOT - Un seul point de vérité).
  • Un fichier source ne contient jamais extern déclarations de variables - les fichiers source incluent 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'initialiser aussi. (Bien qu'il n'y ait pas besoin de 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'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 cohérentes.
  • 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        SOQ (Stack Overflow Questions)       référentiel sur GitHub dans le        src / so-0143-3204       sous-répertoire.

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

Pas si bon moyen de définir des variables globales

Avec certains (en fait, beaucoup) de compilateurs C, vous pouvez vous en sortir avec ce qui est appelé une définition «commune» d'une variable aussi. «Commun», ici, se réfère à une technique utilisée dans Fortran pour le partage des variables entre les fichiers source, en utilisant un bloc COMMON (éventuellement nommé). Qu'est-ce qui se passe ici est que chacun d'un certain nombre de fichiers fournit une tentative définition de la variable. Tant que pas plus d'un fichier fournit une définition initialisée, puis 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 C et à la 'une règle de définition' - c'est un comportement officiellement indéfini:

J.2 Comportement indéfini

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

§6.9 Définitions externes ¶5

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

161) Ainsi, si un identifiant est déclaré avec un lien externe   n'est pas utilisé dans une expression, il n'y a pas besoin de définition externe pour   il.

Cependant, la norme C l'inscrit également dans l'annexe J informative la Extensions communes.

J.5.11 Définitions externes multiples

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

Parce que cette technique n'est pas toujours supportée, il vaut mieux éviter En l'utilisant, surtout si votre code doit être portable. En utilisant cette technique, vous pouvez également vous retrouver avec un type involontaire punition Si l'un des fichiers a été déclaré i comme un double au lieu de int, Les linkers dangereux de type C ne détecteraient probablement pas la discordance. Si vous êtes sur une machine avec 64 bits int et double, tu n'aurais même pas obtenir un avertissement; sur une machine avec 32 bits int et 64 bits double, vous auriez probablement obtenir un avertissement sur les différentes tailles - l'éditeur de liens utiliser la plus grande taille, exactement comme un programme Fortran prendrait la la plus grande taille de tous les blocs communs.


Les deux fichiers suivants complètent la source pour 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 les usages prog2.c, file10.c, file11.c, file12.c, prog2.h.

Attention

Comme indiqué dans les commentaires ici, et comme indiqué dans ma réponse à un similaire question, en utilisant plusieurs les définitions pour une variable globale mènent à un comportement indéfini (J.2; §6.9), qui est la manière standard de dire "tout peut arriver". Une des choses qui peut arriver est que le programme se comporte comme vous attendre; et J.5.11 dit, approximativement, "vous pourriez être chanceux plus souvent que vous 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 strictement programme conforme et pas garanti de travailler partout. Equivalently: il contient un bug qui peut ou peut ne pas se montrer.

Violer les lignes directrices

Il y a, bien sûr, plusieurs façons dont ces directives peuvent être brisées. Occasionnellement, il peut y avoir une bonne raison d'enfreindre les directives, mais ces occasions sont extrêmement inhabituelles.

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 extern mot-clé, puis chaque fichier qui comprend 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 garantir que cela fonctionnera.

broken_header.h

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

Note 2: si l'entê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 servent principalement à partager de l'information, c'est un peu idiot pour en créer un qui ne peut être utilisé qu'une seule fois.

rarement_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 termine par son propre version de la variable 'globale'.

Si la variable est en réalité un tableau complexe, par exemple, cela peut conduire à la duplication extrême du code. Il peut, très occasionnellement, être un manière sensée de réaliser un certain effet, mais c'est très inhabituel.


Résumé

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 global_variable est inclus dans chaque fichier qui l'utilise - y compris celui qui le définit. Cela garantit que tout est auto-cohérent.

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

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 parfois (et légitimement) soulevée à propos de "déclarations dans les en-têtes, définitions dans la source" mécanisme décrit voici qu'il y a deux fichiers à garder synchronisés - l'en-tête et la source. Ceci est généralement suivi d'une observation qu'un macro peut être utilisé de sorte que l'en-tête sert double devoir - normalement déclarer les variables, mais quand une macro spécifique est définie avant la 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 principaux». Ceci est normalement une préoccupation fausse; toi 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 pour 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 les usages prog3.c, file1a.c, file2a.c, file3a.h, prog3.h.

Initialisation de variable

Le problème avec ce schéma comme montré est qu'il ne prévoit pas initialisation de la variable globale. Avec C99 ou C11 et argument variable listes pour les macros, vous pouvez définir une macro pour prendre en charge l'initialisation aussi. (Avec C89 et pas de support pour les 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 de #if et #else blocs, fixation 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;
}

Clairement, le code pour la structure de l'excentrique n'est pas ce que vous auriez normalement écrire, mais cela illustre le point. Le premier argument à la seconde 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, 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 pour 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 les usages prog4.c, file1b.c, file2b.c, prog4.h, file3b.h.

En-têtes

Tout en-tête devrait être protégé contre la réinclusion, de sorte que le type définitions (enum, struct ou union types, ou typedefs généralement) ne pas causer des problèmes. La technique standard consiste à envelopper le corps de en-tête dans une garde d'en-tête comme:

#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 qui n'est pas montrée, et file1b.cdoit utiliser les deux en-tête file4b.h et file3b.h, puis vous avez des problèmes plus complexes à résoudre. Clairement, vous pourriez réviser la liste d'en-tête à inclure juste file4b.h. Cependant, vous pourriez ne pas être conscient des dépendances internes - et le code devrait, idéalement, continue de travailler.

En outre, il commence à être difficile parce que vous pourriez inclure file4b.h avant d'inclure file3b.h pour générer les définitions, mais la normale gardes d'en-tête sur file3b.h empêcherait l'en-tête d'être réintroduit.

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

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 EXTERN, etc.
  • file1c.h 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 et ensuite définir les variables globales.
  • file6c.c ce qui montre que vous pouvez définir et ensuite (tenter de) déclarer les variables globales.

Dans ces exemples, file5c.c et file6c.c inclure directement l'en-tête file2c.h plusieurs fois, mais c'est le moyen le plus simple de montrer que 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:

  1. L'en-tête définissant ou déclarant les variables globales peut ne pas être lui-même définir tous les types.
  2. Immédiatement 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 <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 les usages prog5.c, file3c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog6 les usages prog5.c, file5c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog7 les usages 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 les variables (telles que file2c.h) est inclus par un autre en-tête file7c.h) qui définit les variables. Il n'y a pas un moyen facile autour de cette autre que "ne le fais pas".

Vous pouvez partiellement contourner le problème en révisant file2c.h dans 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 'devrait l'en-tête inclure #undef DEFINE_VARIABLES? ' Si vous l'omettez de l'en-tête et enveloppez 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 Je dois me 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 <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 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 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 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 les usages prog8.c, file7c.c, file9c.c.
  • prog9 les usages prog8.c, file8c.c, file9c.c.

Cependant, les problèmes sont relativement peu susceptibles de se produire dans la pratique, surtout si vous prenez le conseil standard à

Éviter les variables globales


Cette exposition manque-t-elle quelque chose?

Confession: Le schéma «éviter le code dupliqué» décrit ici était développé parce que le problème affecte un code sur lequel je travaille (mais que je ne possède pas), et est une préoccupation agaçante avec le régime décrit dans la première partie de la réponse. Cependant, le schéma original vous laisse avec seulement deux endroits à modifier pour conserver les définitions et les déclarations variables synchronisé, ce qui est un grand pas en avant d'avoir une variable exernal déclarations 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 le fichiers avec les noms fileNc.[ch] (plus external.h et externdef.h) montre qu'il peut être fait pour travailler. 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 une variable définissant et déclarant un fichier d'en-tête.

NB Ce sont des programmes de jouets avec juste assez de code pour les rendre marginalement intéressant. Il y a répétition dans les exemples que pourrait être enlevé, mais ne vise pas à 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 qui sont inclus. Il serait possible de réorganiser le code de sorte que le main() la fonction n'a pas été répétée, mais il cacherait plus qu'il ne l'a révélé.)


1482
2017-09-16 14:37



Un extern variable 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-des dossiers 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 tu dois utiliser 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
2017-09-16 14:12



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 et ensuite y accéder à partir d'une autre, puis dans la seconde, vous le déclarez comme extern et le symbole sera résolu par l'éditeur de liens.

Si vous ne le déclarez pas comme externe, vous obtiendrez 2 variables nommées de la même manière, mais pas du tout liées, et une erreur de plusieurs définitions de la variable.


34
2017-09-16 14:11



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

Lors de la rencontre d'un extern, le compilateur peut seulement trouver son type, pas où il "vit", donc il ne peut pas résoudre la référence.

Vous dites: "Faites-moi confiance, au moment de la liaison, cette référence sera résolue".


23
2017-09-16 14:50



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 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 vérifie pas.


17
2017-09-16 14:18



Ajouter un extern tourne une variable définition dans une variable déclaration. Voir ce fil quant à la différence entre une déclaration et une définition.


15
2017-09-16 14:16



L'interprétation correcte d'extern est que vous dites quelque chose au compilateur. Vous dites au compilateur que, bien qu'il ne soit pas présent en ce moment, la variable déclarée sera en quelque sorte trouvée par l'éditeur de liens (généralement dans un autre objet (fichier)). L'éditeur de liens sera alors le gars chanceux pour trouver tout et le mettre ensemble, si vous avez eu des déclarations externes ou non.


11
2018-06-20 23:43



En C, une variable à l'intérieur d'un fichier, par exemple example.c, a une portée locale. Le compilateur s'attend à ce que la variable ait sa définition dans le même fichier example.c et quand elle ne trouve pas la même chose, elle enverrait une erreur. Une fonction d'un autre côté a par défaut une portée globale. Ainsi, vous n'avez pas à mentionner explicitement au compilateur "look mec ... vous pourriez trouver la définition de cette fonction ici". Pour une fonction comprenant le fichier qui contient sa déclaration est suffisant. (Le fichier que vous appelez réellement un fichier d'en-tête).    Par exemple considérons 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 example.c example1.c étape 2) ./ ex

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


8
2017-07-02 09:11



Le mot-clé extern est utilisé avec la variable pour son identification en tant que variable globale.


7
2017-08-20 10:19



Implémentation de GCC ELF Linux

main.c:

#include <stdio.h>

int not_extern_int = 1;
extern int extern_int;

void main() {
    printf("%d\n", not_extern_int);
    printf("%d\n", extern_int);
}

Compiler et décompiler:

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

La sortie contient:

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

le Système V ABI Update ELF spec Le chapitre "Table des symboles" explique:

SHN_UNDEF Cet index de table de section signifie que le symbole est indéfini. Lorsque l'éditeur de liens combine ce fichier objet avec un autre qui définit le symbole indiqué, les références de ce fichier au symbole seront liées à la définition réelle.

qui est essentiellement le comportement que le standard C donne à extern variables

A partir de maintenant, c'est le travail du linker de faire le programme final, mais le extern les informations ont déjà été extraites du code source dans le fichier objet.

Testé sur GCC 4.8.


5
2018-05-29 07:34