Je sais que les variables globales en C ont parfois le mot clé extern
. Qu'est-ce qu'une variable extern
? À quoi ressemble la déclaration? Quelle est sa portée?
Cela est lié au partage des variables entre les fichiers source, mais comment cela fonctionne-t-il précisément? Où est-ce que j'utilise extern
?
L'utilisation de extern
n'est utile que lorsque le programme que vous créez est composé de plusieurs fichiers source liés, certaines des variables définies, par exemple, dans le fichier source file1.c
doivent être référencées dans un autre fichier source. fichiers, tels que file2.c
.
Il est important de comprendre la différence entre définir une variable et déclarer une variable :
Vous pouvez déclarer une variable plusieurs fois (une seule fois suffit); vous ne pouvez le définir qu'une seule fois dans une portée donnée. Une définition de variable est également une déclaration, mais toutes les déclarations de variable ne sont pas des définitions.
Le moyen propre et fiable de déclarer et de définir des variables globales consiste à utiliser un fichier d’en-tête contenant 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 font référence à la variable. Pour chaque programme, un fichier source (et un seul fichier source) définit la variable. De même, un fichier d'en-tête (et un seul fichier d'en-tête) doit déclarer la variable. Le fichier d'en-tête est crucial. il permet une vérification croisée entre les UT indépendantes (unités de traduction - pensez aux fichiers source) et garantit la cohérence.
Bien qu'il existe d'autres moyens de le faire, cette méthode est simple et fiable. Il est démontré par file3.h
, file1.c
et file2.c
:
extern int global_variable; /* Declaration of the variable */
#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++; }
#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 le source pour prog1
:
Les programmes complets illustrés utilisent des fonctions. Par conséquent, les déclarations de fonction se sont glissées. C99 et C11 exigent que les fonctions soient déclarées ou définies avant d'être utilisées (contrairement à C90, pour de bonnes raisons). J'utilise le mot clé extern
devant les déclarations de fonctions dans les en-têtes pour des raisons de cohérence - pour faire correspondre le extern
devant les déclarations de variables dans les en-têtes. Beaucoup de gens préfèrent ne pas utiliser extern
devant les déclarations de fonctions; le compilateur s'en fiche - et au final, moi non plus, tant que vous êtes cohérent, du moins dans un fichier source.
extern void use_it(void);
extern int increment(void);
#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
.Le fichier prog1.mk
est un fichier makefile pour prog1
uniquement. Cela fonctionnera avec la plupart des versions de make
produites depuis le tournant du millénaire. Il n’est pas lié spécifiquement à GNU Make.
# 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}
Les règles ne doivent être enfreintes que par des experts, et avec raison:
extern
déclarations de variables - jamais static
ou des définitions de variables non qualifiées.extern
déclarations de variables - les fichiers source incluent toujours l'en-tête (unique) qui les déclare.extern
.Le code source et le texte de cette réponse sont disponibles dans le référentiel SOQ (Stack Overflow Questions) de GitHub dans le répertoire src/so-0143-3204 sous-répertoire.
Si vous n'êtes pas un programmeur C expérimenté, vous pouvez (et devriez peut-être) arrêter de lire ici.
Avec certains (en fait, beaucoup) compilateurs C, vous pouvez également vous échapper avec 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 les fichiers source, en utilisant un bloc COMMON (éventuellement nommé). Ce qui se passe ici est que chacun d’un nombre de fichiers fournit une définition provisoire de la variable. Tant qu'un seul fichier fournit une définition initialisée, les différents fichiers partagent une définition unique et commune de la variable:
#include "prog2.h"
int i; /* Do not do this in portable code */
void inc(void) { i++; }
#include "prog2.h"
int i; /* Do not do this in portable code */
void dec(void) { i--; }
#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" - il s'agit d'un comportement officiellement indéfini:
Un identifiant avec une liaison externe est utilisé, mais dans le programme, il n'existe pas exactement une définition externe pour l'identifiant, ou l'identifiant n'est pas utilisé et il existe plusieurs définitions externes pour l'identifiant (6.9).
Une définition externe est une déclaration externe qui est également une définition d'une fonction (autre qu'une définition en ligne) ou d'un objet. Si un identificateur déclaré avec une liaison externe est utilisé dans une expression (autre que dans l'opérande d'un opérateur
sizeof
ou_Alignof
dont le résultat est une constante entière), il doit exister quelque part dans le programme entier exactement une définition externe pour l'identifiant; sinon, il n'y en aura pas plus d'un.161)161) Ainsi, si un identifiant déclaré avec une liaison externe n'est pas utilisé dans une expression, il ne doit pas y avoir de définition externe pour celui-ci.
Cependant, la norme C la mentionne également dans l’annexe informative J comme l’un des extensions communes .
J.5.11 Définitions externes multiples
Il peut exister plusieurs définitions externes 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 d'entre elles sont initialisées, le comportement est indéfini (6.9.2).
Comme cette technique n'est pas toujours supportée, il est préférable d'éviter de l'utiliser , surtout si votre code doit être portable . En utilisant cette technique, vous pouvez également vous retrouver avec une punition de type non intentionnelle. Si l'un des fichiers déclarait i
comme double
au lieu de comme int
, les lieurs non sécuritaires avec le type de C ne détecteraient probablement pas l'incompatibilité. Si vous êtes sur une machine avec int
et double
64 bits, vous ne recevrez même pas d'avertissement. sur une machine avec int
32 bits et double
32 bits, vous obtiendrez probablement un avertissement concernant les différentes tailles - l'éditeur de liens utiliserait la plus grande taille, exactement comme le ferait un programme Fortran. plus grande taille de tous les blocs communs.
Les deux fichiers suivants complètent le source pour prog2
:
extern void dec(void);
extern void put(void);
extern void inc(void);
#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
.Comme noté dans les commentaires ici, et comme indiqué dans ma réponse à une question similaire, utiliser plusieurs définitions pour une variable globale conduit à un comportement indéfini (J.2; §6.9), qui est le moyen de standard en disant "tout peut arriver". Une des choses qui peut arriver est que le programme se comporte comme vous le souhaitez. et J.5.11 dit, approximativement, "vous pourriez avoir de la chance plus souvent que vous ne le méritez". Mais un programme qui repose sur plusieurs définitions d'une variable extern - avec ou sans le mot clé explicite 'extern' - n'est pas un programme strictement conforme et il n'est pas garanti qu'il fonctionne partout. De manière équivalente: il contient un bug qui peut ou non se montrer.
Bien entendu, ces directives peuvent être enfreintes de nombreuses façons. Parfois, il peut y avoir une bonne raison d'enfreindre les directives, mais de telles occasions sont extrêmement inhabituelles.
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 comprenant l'en-tête crée une définition provisoire de la variable. Comme indiqué précédemment, cela fonctionnera souvent, mais la norme C ne garantit pas son efficacité.
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, seul un fichier source d'un programme donné peut utiliser l'en-tête. Étant donné que les en-têtes servent principalement à partager des informations, il est un peu ridicule de créer un fichier utilisable une seule fois.
static int hidden_global = 3; /* Each source file gets its own copy */
Remarque 3: si l'en-tête définit une variable statique (avec ou sans initialisation), chaque fichier source se voit attribuer sa propre version privée de la variable "globale".
Si la variable est en réalité un tableau complexe, par exemple, cela peut entraîner une duplication extrême du code. Cela peut très occasionnellement être un moyen judicieux d’obtenir un effet, mais c’est très inhabituel.
Utilisez la technique d'en-tête que j'ai montrée en premier. Cela fonctionne de manière fiable et partout. Notez en particulier que l'en-tête déclarant le global_variable
est inclus dans chaque fichier qui l'utilise - y compris celui qui le définit. Cela garantit que tout est cohérent.
Des préoccupations similaires se posent avec la déclaration et la définition de fonctions - des règles analogues s’appliquent. Mais la question portait spécifiquement sur les variables, je n'ai donc gardé que la réponse aux variables.
Si vous n'êtes pas un programmeur C expérimenté, vous devriez probablement arrêter de lire ici.
Ajout majeur tardif
Une préoccupation qui est parfois (et légitimement) soulevée au sujet du mécanisme "déclarations dans en-têtes, définitions dans source" décrit ici est qu'il y a deux fichiers à garder synchronisés - l'en-tête et la source. Cela est généralement suivi d'une observation selon laquelle une macro peut être utilisée de sorte que l'en-tête remplit une double fonction - en déclarant normalement les variables, mais lorsqu'une macro spécifique est définie avant que l'en-tête soit inclus, elle définit plutôt les variables.
Une autre préoccupation peut être que les variables doivent être définies dans chacun d’un certain nombre de "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
:
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable;
#define DEFINE_VARIABLES
#include "file3a.h" /* Variable defined - but not initialized */
#include "prog3.h"
int increment(void) { return global_variable++; }
#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 le source pour prog3
:
extern void use_it(void);
extern int increment(void);
#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
.Le problème avec ce schéma, comme indiqué, est qu'il ne prévoit pas l'initialisation de la variable globale. Avec C99 ou C11 et les listes d’arguments variables pour les macros, vous pouvez également définir une macro pour prendre en charge l’initialisation. (Avec C89 et l'absence de prise en charge des listes d'arguments variables dans les macros, il n'existe pas de moyen facile de gérer des initialiseurs arbitrairement longs.)
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZER(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZER(...) /* nothing */
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable INITIALIZER(37);
EXTERN struct { int a; int b; } oddball_struct INITIALIZER({ 41, 43 });
Inverser le contenu des blocs #if
et #else
, correction du bogue identifié par Denis Kniazhev
#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; }
#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 oddball n’est pas celui que vous écrivez normalement, mais il illustre bien ce point. Le premier argument de la seconde invocation de INITIALIZER
est { 41
et le dernier argument (singulier dans cet exemple) est 43 }
. Sans C99 ou un support similaire pour les listes d'arguments variables pour les macros, les initialiseurs devant 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 le source pour prog4
:
extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);
#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
.Tout en-tête doit être protégé contre la réinclusion, afin que les définitions de type (types énum, 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 non affichée et file1b.c
doit utiliser les deux en-têtes file4b.h
et file3b.h
, avoir des problèmes plus délicats à résoudre. De toute évidence, vous pouvez modifier la liste des en-têtes afin d’inclure uniquement file4b.h
. Cependant, vous pouvez ne pas être au courant des dépendances internes - et le code devrait, dans l'idéal, continuer à fonctionner.
En outre, cela commence à devenir délicat car vous pouvez inclure file4b.h
avant d'inclure file3b.h
pour générer les définitions, mais les gardes d'en-tête habituels sur file3b.h
empêchent la réinclusion de l'en-tête.
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 aurez peut-être besoin des deux en une seule unité de traduction (TU) - une combinaison d'un fichier source et des en-têtes correspondants. les usages).
Cependant, cela peut être fait avec 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 indique que vous pouvez déclarer, puis définir les variables globales.file6c.c
qui indique que vous pouvez définir puis (tenter de) déclarer les variables globales.Dans ces exemples, file5c.c
et file6c.c
incluent directement l'en-tête file2c.h
plusieurs fois, mais c'est 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:
/*
** 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 */
#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 */
/* 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 */
#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; }
#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;
}
#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; }
#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
:
#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 un problème uniquement si un en-tête qui définit des variables (tel que file2c.h
) est inclus par un autre en-tête (disons file7c.h
) qui définit des variables. Il n'y a pas de moyen facile de contourner cela, à part "ne le fais pas".
Vous pouvez partiellement contourner le problème en modifiant file2c.h
en 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 de l'en-tête et encapsulez toute invocation de définition avec #define
et #undef
:
#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES
dans le code source (pour que les en-têtes ne modifient jamais la valeur de DEFINE_VARIABLES
]), vous devez alors être pur. N’oubliez pas d’écrire la ligne en plus. Une alternative pourrait être:
#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "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 sécurisé (en utilisant le file2d.h
, sans #undef DEFINE_VARIABLES
dans le file2d.h
).
/* 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; }
/* 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 */
/* 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 le source pour prog8
et prog9
:
#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;
}
#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, il est relativement peu probable que les problèmes se posent dans la pratique, surtout si vous suivez les conseils habituels.
Cette exposition manque-t-elle quelque chose?
Confession : le schéma "éviter le code dupliqué" décrit ici a été développé parce que le problème affecte certains codes sur lesquels je travaille (mais que je ne possède pas), et constitue un sujet d'inquiétude pour le schéma. décrites dans la première partie de la réponse. Cependant, le schéma d'origine ne vous laisse que deux emplacements à modifier pour conserver les définitions de variable et les déclarations synchronisées, ce qui constitue un grand pas en avant par rapport à la dispersion des déclarations de variables externes dans la base de code (ce qui compte vraiment lorsqu'il y a des milliers de fichiers au total). . Cependant, le code dans les fichiers portant les noms fileNc.[ch]
(plus external.h
et externdef.h
)) indique qu'il peut fonctionner correctement. De toute évidence, il ne serait pas difficile de créer un script générateur d'en-tête pour vous fournir le modèle standardisé pour une variable définissant et déclarant un fichier d'en-tête.
NB Ce sont des programmes-jouets contenant à peine assez de code pour les rendre légèrement intéressants. Il y a une répétition dans les exemples qui pourraient être supprimés, mais cela ne simplifie pas l'explication pédagogique. (Par exemple: la différence entre prog5.c
et prog8.c
est le nom de l'un des en-têtes inclus. Il serait possible de réorganiser le code pour que la fonction main()
ne soit pas répétée. , mais il en cacherait plus qu'il n'en révélait.)
Une variable extern
est une déclaration (grâce à sbi pour la correction) d'une variable définie dans une autre unité de traduction. Cela signifie que la mémoire de la variable est allouée dans un autre fichier.
Supposons que vous ayez deux .c
- fichiers test1.c
et test2.c
. Si vous définissez une variable globale int test1_var;
dans test1.c
et que 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 est le mot clé que vous utilisez pour déclarer que la variable elle-même réside dans une autre unité de traduction.
Vous pouvez donc décider d'utiliser une variable dans une unité de traduction, puis d'y accéder à partir d'une autre, puis de la déclarer dans la seconde comme externe et le symbole sera résolu par l'éditeur de liens.
Si vous ne le déclarez pas en tant qu'extern, vous obtiendrez 2 variables nommées de la même manière mais non liées du tout, ainsi qu'une erreur de plusieurs définitions de la variable.
J'aime penser à une variable externe comme une promesse faite au compilateur.
Lorsqu'il rencontre un externe, le compilateur ne peut que connaître son type, pas où il "réside", il ne peut donc pas résoudre la référence.
Vous lui dites: "Faites-moi confiance. Au moment du lien, cette référence sera résolue."
extern dit au compilateur de vous faire confiance que la mémoire de cette variable est déclarée ailleurs, aussi ne tente-t-elle pas d'allouer/vérifier la mémoire.
Par conséquent, vous pouvez compiler un fichier qui fait référence à un externe, mais vous ne pouvez pas créer de lien si cette mémoire n'est pas déclarée quelque part.
Utile pour les variables globales et les bibliothèques, mais dangereuse car l'éditeur de liens ne tapez pas check.
Ajouter une extern
transforme une définition de variable en une déclaration de variable . Voir ce fil pour savoir quelle est la différence entre une déclaration et une définition.
L’interprétation correcte de extern est que vous dites quelque chose au compilateur. Vous dites au compilateur que, même si elle n’est pas présente pour le 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 lien sera alors l'heureux chanceux de tout trouver et de tout mettre en ordre, que vous ayez eu des déclarations externes ou non.
declare | define | initialize |
----------------------------------
extern int a; yes no no
-------------
int a = 2019; yes yes yes
-------------
int a; yes yes no
-------------
La déclaration n'allouera pas de mémoire (la variable doit être définie pour l'allocation de mémoire) mais la définition le fera. Ceci est juste une autre vue simple sur le mot-clé extern puisque les autres réponses sont vraiment géniales.
En C, une variable dans 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, lorsqu'elle ne trouve pas la même chose, génère une erreur. Par contre, une fonction a une portée globale. Ainsi, vous n'avez pas à mentionner explicitement au compilateur "regarde mec ... tu pourrais trouver la définition de cette fonction ici". Pour une fonction incluant le fichier qui contient sa déclaration, cela suffit (le fichier que vous appelez en réalité 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 exemple.c exemple1.c étape 2) ./ ex
Vous obtenez le résultat suivant: La valeur de a est <5>
le mot clé extern est utilisé avec la variable pour son identification en tant que variable globale.
Cela signifie également que vous pouvez utiliser la variable déclarée à l'aide du mot clé extern dans n'importe quel fichier, même si elle est déclarée/définie dans un autre fichier.
Implémentation 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 chapitre Spécification ELF de la mise à jour ABI System V "Table des symboles" explique:
SHN_UNDEF Cet index de table de section signifie que le symbole n'est pas défini. Lorsque l'éditeur de lien combine ce fichier objet avec un autre qui définit le symbole indiqué, les références de ce fichier au symbole sont liées à la définition réelle.
qui est fondamentalement le comportement que le standard C donne à extern
variables.
Il appartient désormais à 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.
Testé sur GCC 4.8.
C++ 17 variables en ligne
En C++ 17, vous pouvez utiliser des variables en ligne plutôt que des variables externes, car elles sont simples à utiliser (ne peuvent être définies qu'une seule fois sur l'en-tête) et plus puissantes (support constexpr). Voir: Que signifie 'const statique' en C et C++?
extern
signifie simplement qu'une variable est définie ailleurs (par exemple, dans un autre fichier).
extern
permet à un module de votre programme d'accéder à une variable globale ou à une fonction déclarée dans un autre module de votre programme. Vous avez généralement des variables externes déclarées dans les fichiers d'en-tête.
Si vous ne souhaitez pas qu'un programme accède à vos variables ou fonctions, utilisez static
qui indique au compilateur que cette variable ou cette fonction ne peut pas être utilisée en dehors de ce module.
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
permet à d'autres fichiers C ou à des composants externes de savoir que cette variable est déjà définie quelque part. Exemple: si vous construisez une bibliothèque, inutile de définir obligatoirement la variable globale quelque part dans la bibliothèque elle-même. La bibliothèque sera compilée directement, mais lors de la liaison du fichier, elle vérifie la définition.
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
.
La extern
peut être déclarée dans le fichier first.c
ou dans l'un des fichiers d'en-tête first.c
inclus.
Avec xc8, vous devez faire attention à ne pas déclarer une variable du même type dans chaque fichier car vous pourriez, à tort, déclarer quelque chose comme int
dans un fichier et un char
dire dans un autre. Cela pourrait conduire à la corruption de variables.
Ce problème a été résolu avec élégance dans un forum sur une puce il y a une quinzaine d'années/* Voir "http: www.htsoft.com" // "forum/all/showflat.php/Cat/0/Numéro/18766/an/0/page/0 # 18766 "
Mais ce lien ne semble plus fonctionner ...
Je vais donc rapidement essayer de l'expliquer. faire un fichier appelé global.h.
Dans ce déclarez 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 main.c
#define MAIN_C 1
#include "global.h"
#undef MAIN_C
Cela signifie que dans main.c la variable sera déclarée comme un unsigned char
.
Maintenant, dans d'autres fichiers, simplement, y compris global.h, sera déclaré externe pour ce fichier .
extern unsigned char testing_mode;
Mais il sera correctement déclaré en tant que unsigned char
.
Le vieil article du forum l'expliquait probablement un peu plus clairement. Mais ceci est un potentiel réel gotcha
lorsque vous utilisez un compilateur qui vous permet de déclarer une variable dans un fichier, puis de la déclarer externe sous un type différent dans un autre. Si vous dites que testing_mode est déclaré comme un int dans un autre fichier, il pourrait penser qu'il s'agissait d'une variable 16 bits et écraser une autre partie de la RAM, ce qui pourrait potentiellement corrompre une autre variable. Difficile de déboguer!
Une solution très courte que j'utilise pour permettre à un fichier d'en-tête de contenir la référence externe ou l'implémentation réelle d'un objet. Le fichier qui contient réellement l'objet ne fait que #define GLOBAL_FOO_IMPLEMENTATION
. Ensuite, lorsque j'ajoute un nouvel objet à ce fichier, il apparaît également dans ce fichier sans que je doive copier et coller la définition.
J'utilise ce modèle sur plusieurs fichiers. Donc, afin de garder les choses aussi autonomes que possible, je viens de réutiliser la seule macro GLOBAL dans chaque en-tête. Mon en-tête ressemble à ceci:
//file foo_globals.h
#pragma once
#include "foo.h" //contains definition of foo
#ifdef GLOBAL
#undef GLOBAL
#endif
#ifdef GLOBAL_FOO_IMPLEMENTATION
#define GLOBAL
#else
#define GLOBAL extern
#endif
GLOBAL Foo foo1;
GLOBAL Foo foo2;
//file main.cpp
#define GLOBAL_FOO_IMPLEMENTATION
#include "foo_globals.h"
//file uses_extern_foo.cpp
#include "foo_globals.h