programmation - variable externe c




Comment utiliser extern pour partager des variables entre des fichiers source? (10)

Je sais que les variables globales dans C ont parfois le mot-clé extern . Qu'est-ce qu'une variable extern ? À 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 ?


mais comment cela fonctionne-t-il précisément?

Voyons comment GCC 4.8 ELF l'implémente

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 chapitre "Table des symboles " de la spécification ELF System V ABI Update 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.

ce qui est essentiellement le comportement que le standard C donne aux variables extern .

À partir de maintenant, c'est le travail de l'éditeur de liens de créer le programme final, mais les informations extern ont déjà été extraites du code source dans le fichier objet.


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 incluant 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 exemple1.c étape 2) ./ ex

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


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


L'ajout d'un extern transforme une définition de variable en une déclaration de variable. Voir ce sujet pour savoir quelle est la différence entre une déclaration et une définition.


L'utilisation d' extern n'est pertinente que lorsque le programme que vous construisez est constitué de plusieurs fichiers source relié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 la définition d' une variable et la déclaration d' 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 moment-là.
  • Une variable est définie lorsque le compilateur alloue le stockage pour la variable.

Vous pouvez déclarer une variable plusieurs fois (bien qu'une 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 toutes les déclarations de variable ne sont pas des définitions.

Le meilleur moyen de déclarer et de définir des variables globales

Bien qu'il existe d'autres façons de le faire, la manière propre et fiable de déclarer et de définir des variables globales consiste à utiliser un fichier d'en-tête file3.h pour contenir une déclaration extern de la variable. L'en-tête est inclus par le fichier source unique qui définit la variable et par tous les fichiers source qui référencent la variable. Pour chaque programme, un fichier source (et un seul fichier source) définit la variable. De même, un fichier d'en-tête (et un seul fichier d'en-tête) doit déclarer la variable.

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 la meilleure façon de les utiliser.

Les deux fichiers suivants complètent la source de prog1 :

Notez que j'utilise le mot-clé extern devant les déclarations de fonction dans les en-têtes (comme montré dans prog1.h , par exemple) 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 fonctions; le compilateur s'en fout - et finalement, je ne le fais pas non plus tant que vous êtes cohérent.

prog1.h

extern void use_it(void);   // "extern" is optional here; see note above
extern int increment(void); // "extern" is optional here; see note above

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 utilise prog1.c , file1.c , file2.c , file3.h et prog1.h .

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 des déclarations extern de variables - jamais de définitions de variables static ou non qualifiées.
  • Pour une 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éclarations extern de variables - les fichiers sources incluent toujours l'en-tête (unique) qui les déclare.
  • Pour une variable donnée, exactement un fichier source définit la variable, préférablement l'initialiser aussi. (Bien qu'il ne soit pas nécessaire d'initialiser explicitement à zéro, cela 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 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.

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

Le code source et le texte de cette réponse sont disponibles dans mon SOQ ( Questions) sur GitHub dans le sous-répertoire src/so-0143-3204 .

Pas si bon moyen de définir des variables globales

Avec certains (en fait beaucoup) de compilateurs C, vous pouvez aussi vous passer de ce que l'on appelle une définition «commune» d'une variable. «Commun», ici, fait référence à une technique utilisée dans Fortran pour partager des variables entre des fichiers sources, en utilisant un bloc COMMON (éventuellement nommé). Ce qui se passe ici est que chacun d'un certain nombre de fichiers fournit une définition provisoire de la variable. Tant que pas plus d'un fichier fournit une définition initialisée, les différents fichiers finissent par partager une seule définition 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 «règle de définition unique», mais la norme C la liste comme une variation commune de sa règle de définition unique. Cette technique n'étant pas toujours prise en charge, il est préférable de ne pas l'utiliser, surtout si votre code doit être portable . En utilisant cette technique, vous pouvez également vous retrouver avec des punitions de type involontaires. Si l'un des fichiers déclarait i comme un double au lieu d'un int , les liens de type C dangereux pour C ne détecteraient probablement pas la discordance. Si vous utilisez une machine avec un int et un double 64 bits, vous n'obtiendrez même pas d'avertissement. Sur une machine avec un int 32 bits et un double 64 bits, vous obtiendrez probablement un avertissement sur les différentes tailles - l'éditeur de liens utiliserait la plus grande taille, exactement comme un programme Fortran prendrait la plus grande taille de tous les blocs communs.

Ceci est mentionné dans la norme C de l'annexe J informative comme une extension commune:

J.5.11 Définitions externes multiples

Il peut y avoir plus d'une définition externe pour l'identifiant d'un objet, 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).

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 utilise prog2.c , file10.c , file11.c , file12.c , prog2.h .

Attention

Comme indiqué dans les commentaires ici, et comme indiqué dans ma réponse à une question similaire, l'utilisation de plusieurs définitions pour une variable globale conduit à un comportement indéfini, ce qui est la manière standard de dire "tout peut arriver". Une des choses qui peut arriver est que le programme se comporte comme prévu; et J.5.11 dit, approximativement, "vous pourriez être chanceux plus souvent que vous ne le méritez". Mais un programme qui repose sur plusieurs définitions d'une variable externe - avec ou sans le mot clé explicite 'extern' - n'est pas un programme strictement conforme et n'est pas garanti pour fonctionner partout. Equivalently: il contient un bug qui peut ou peut ne pas se montrer.

Violer les lignes directrices

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.

broken_header.h

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

Remarque 2: si l'en-tête définit et initialise la variable, un seul fichier source d'un programme donné peut utiliser l'en-tête.

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), alors chaque fichier source se retrouve avec sa propre version privée de la variable 'global'.

Si la variable est en réalité un tableau complexe, par exemple, cela peut conduire à une duplication extrême du code. Il peut, très occasionnellement, être un moyen sensé d'obtenir un effet, mais c'est plutôt 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é la réponse aux variables seulement.

(Les programmes complets utilisent des fonctions, donc les déclarations de fonction se sont glissées.) J'utilise le mot-clé extern devant les déclarations de fonction dans les en-têtes pour faire correspondre l' extern devant les déclarations de variables dans les en-têtes. le compilateur s'en fout - et finalement, je ne le fais pas non plus tant que vous êtes cohérent.)

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 «déclarations dans les en-têtes, définitions dans la source» décrit ici est 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'une macro peut être utilisée pour que l'en-tête serve à double devoir - normalement en déclarant les variables, mais lorsqu'une macro spécifique est définie avant l'inclusion de l'en-tête, elle définit les variables à la place.

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

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

file3a.h

#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */

EXTERN int global_variable;

file1a.c

#define DEFINE_VARIABLES
#include "file3a.h"  /* Variable defined - but not initialized */
#include "prog3.h"

int increment(void) { return global_variable++; }

file2a.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

void use_it(void)
{
    printf("Global variable: %d\n", global_variable++);
}

Les deux fichiers suivants complètent la source de prog3 :

prog3.h

extern void use_it(void);
extern int increment(void);

prog3.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

int main(void)
{
    use_it();
    global_variable += 19;
    use_it();
    printf("Increment: %d\n", increment());
    return 0;
}
  • prog3 utilise 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 l'initialisation de la variable globale. Avec C99 ou C11 et des listes d'arguments variables pour les macros, vous pouvez aussi définir une macro pour supporter l'initialisation. (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 });

#else contenu des blocs #if et #else , corrigeant le 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 de la structure de l'excentrique n'est pas ce que vous écrivez normalement, mais il illustre le point. Le premier argument de 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 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 utilise prog4.c , file1b.c , file2b.c , prog4.h , file3b.h .

En-têtes

Tout en-tête doit être protégé contre la réinclusion, de sorte que les définitions de types (enum, struct ou union, ou typedefs en général) ne posent 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 qui n'est pas affichée, et que file1b.c doit utiliser à la fois l'en-tête file4b.h et file3b.h , vous avez des problèmes plus complexes à résoudre. De toute évidence, vous pouvez réviser la liste d'en-tête pour inclure simplement file4b.h . Cependant, vous ne connaissez peut-être pas les dépendances internes - et le code devrait, idéalement, continuer à fonctionner.

En outre, cela commence à devenir compliqué 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 normaux sur file3b.h empêcheraient l'en-tête d'être réinclus.

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 pouvez avoir besoin des deux dans une seule unité de traduction (TU - 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 EXTERN, 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 et ensuite (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 le moyen le plus simple de montrer que le mécanisme fonctionne. Cela signifie que si l'en-tête était indirectement inclus deux fois, il serait également sûr.

Les restrictions pour que cela fonctionne sont:

  1. L'en-tête définissant ou déclarant les variables globales ne peut lui-même définir aucun type.
  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 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 des variables. Il n'y a pas de moyen facile de contourner cela autrement que de ne pas le faire.

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 «l'en-tête doit-il inclure #undef DEFINE_VARIABLES ? Si vous omettez cela dans l'en-tête et enveloppez tout appel 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 d'avoir à se rappeler d'écrire la ligne supplémentaire. Une alternative pourrait être:

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

externdef.h

/*
** 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 utilise prog8.c , file7c.c , file9c.c .
  • prog9 utilise prog8.c , file8c.c , file9c.c .

Cependant, les problèmes sont relativement peu susceptibles de se produire dans la pratique, surtout si vous prenez les conseils habituels

Éviter les variables globales

Cette exposition manque-t-elle quelque chose?

Confession : Le système «éviter le code dupliqué» décrit ici a été développé parce que le problème affecte un code que je travaille (mais ne possède pas), et est une préoccupation niggling avec le régime décrit dans la première partie de la réponse. Cependant, le schéma d'origine ne vous laisse que deux endroits pour modifier les définitions et les déclarations de variables, ce qui représente un grand pas en avant pour disperser les déclarations de variables exernal dans le code (ce qui compte vraiment quand il y a des milliers de fichiers) . Cependant, le code dans les fichiers avec les noms fileNc.[ch] (plus external.h et externdef.h ) montre qu'il peut être fait fonctionner. De toute évidence, il ne serait pas difficile de créer un script de générateur d'en-tête pour vous donner le modèle normalisé pour un fichier d'en-tête de définition et de déclaration de variable.

NB Ce sont des programmes jouets avec juste assez de code pour les rendre marginalement intéressants. Il y a une répétition dans les exemples qui pourrait être supprimée, mais ce n'est pas pour simplifier l'explication pédagogique. (Par exemple: la différence entre prog5.c et prog8.c est le nom de l'un des en-têtes prog8.c serait possible de réorganiser le code pour que la fonction main() ne soit pas répétée, mais cela cacherait plus que cela a révélé.)


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.

Disons que vous avez deux fichiers test1.c - test2.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 <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

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


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

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


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 compiled directly, but while linking the file, it checks for the definition.





extern