Comment utiliser correctement le mot clé extern en C


236

Ma question est de savoir quand une fonction doit être référencée avec le externmot - clé en C.

Je ne vois pas quand cela devrait être utilisé dans la pratique. Lorsque j'écris un programme, toutes les fonctions que j'utilise sont disponibles via les fichiers d'en-tête que j'ai inclus. Alors, pourquoi serait-il utile d' externavoir accès à quelque chose qui n'était pas exposé dans le fichier d'en-tête?

Je pourrais penser à comment externfonctionne mal, et si c'est le cas, veuillez me corriger.

Edit: Devriez-vous externquelque chose quand c'est la déclaration par défaut sans le mot-clé dans un fichier d'en-tête?


Réponses:


290

" extern" modifie la liaison. Avec le mot-clé, la fonction / variable est supposée être disponible ailleurs et la résolution est reportée à l'éditeur de liens.

Il y a une différence entre "extern" sur les fonctions et sur les variables: sur les variables, il n'instancie pas la variable elle-même, c'est-à-dire qu'il n'alloue aucune mémoire. Cela doit être fait ailleurs. Il est donc important si vous souhaitez importer la variable ailleurs. Pour les fonctions, cela indique uniquement au compilateur que la liaison est externe. Comme il s'agit de la valeur par défaut (vous utilisez le mot clé "statique" pour indiquer qu'une fonction n'est pas liée à l'aide d'une liaison externe), vous n'avez pas besoin de l'utiliser explicitement.


1
alors pourquoi la même chose externe est là dans Git: un logiciel très populaire et moderne le vérifier: github.com/git/git/blob/master/strbuf.h
rsjethani

K&R ne note pas qu'il est par défaut de déclarer la fonction comme "extern", mais cette réponse résout ma confusion!
acgtyrant

@rsjethani Je pense que c'est pour rendre le document plus strict et plus formaté.
acgtyrant

Peut-être une question stupide, mais comment cela se compare-t-il à la déclaration suivante?
weberc2

197

extern indique au compilateur que ces données sont définies quelque part et seront connectées avec l'éditeur de liens.

Avec l'aide des réponses ici et en parlant à quelques amis, voici l'exemple pratique d'une utilisation de extern .

Exemple 1 - pour montrer un piège:

File stdio.h:

int errno;
/* other stuff...*/

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

Si myCFile1.o et myCFile2.o sont liés, chacun des fichiers c a des copies distinctes de errno . C'est un problème car le même errno est censé être disponible dans tous les fichiers liés.

Exemple 2 - Le correctif.

File stdio.h:

extern int errno;
/* other stuff...*/

File stdio.c

int errno;

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

Maintenant, si myCFile1.o et MyCFile2.o sont liés par l'éditeur de liens, ils pointeront tous deux vers le même errno . Ainsi, résoudre l'implémentation avec extern .


71
Le problème n'est pas que les modules myCFile1 et myCFile2 ont une copie séparée de errno, c'est qu'ils exposent tous les deux un symbole appelé "errno". Lorsque l'éditeur de liens voit cela, il ne sait pas quel "errno" choisir, il renflouera donc avec un message d'erreur.
cwick

2
que signifie "lié par l'éditeur de liens"? tout le monde utilise ce terme, je ne trouve aucune définition :(
Marcel Falliere

7
@MarcelFalliere Wiki ~ Compiler compile chaque fichier source de son propre chef et crée un fichier objet pour chaque fichier source. L'éditeur de liens relie ces fichiers objets à 1 exécutable.
Bitterblue

1
@cwick gcc ne donne pas d'erreur ou d'avertissement même après avoir utilisé -Wallet -pedantic. Pourquoi ? et comment ?
b-ak

6
Un garde inclus ne protège-t-il pas contre cette chose exacte?
obskyr

32

Il a déjà été indiqué que le externmot-clé est redondant pour les fonctions.

Quant aux variables partagées entre les unités de compilation, vous devez les déclarer dans un fichier d'en-tête avec le mot-clé extern, puis les définir dans un seul fichier source, sans le mot-clé extern. Le fichier source unique doit être celui qui partage le nom du fichier d'en-tête, pour une meilleure pratique.


@aib "redondant pour les fonctions", consultez mon commentaire dans la réponse de bluebrother.
rsjethani

Que faire si vous ne souhaitez exposer aucune des fonctions du fichier d'en-tête? Ne serait-il pas préférable de déclarer la variable dans un fichier C et d'y accéder par extern dans un autre; laissez l'éditeur de liens résoudre le problème et masquer le reste de l'en-tête.
ste3e

16

Plusieurs années plus tard, je découvre cette question. Après avoir lu chaque réponse et commentaire, j'ai pensé pouvoir clarifier quelques détails ... Cela pourrait être utile pour les personnes qui arrivent ici via la recherche Google.

La question concerne spécifiquement l'utilisation des fonctions "extern", donc j'ignorerai l'utilisation de "extern" avec des variables globales.

Définissons 3 prototypes de fonctions:

//--------------------------------------
//Filename: "my_project.H"
extern int function_1(void);
static int function_2(void);
       int function_3(void);

Le fichier d'en-tête peut être utilisé par le code source principal comme suit:

//--------------------------------------
//Filename: "my_project.C"
#include "my_project.H"

void main(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 1234;

Afin de compiler et de lier, nous devons définir "function_2" dans le même fichier de code source où nous appelons cette fonction. Les deux autres fonctions peuvent être définies dans un code source différent " .C" ou peuvent se trouver dans n'importe quel fichier binaire ( .OBJ, * .LIB, * .DLL), pour lequel nous n'avons peut-être pas le code source.

Incluons à nouveau l'en-tête "mon_projet.H" dans un autre fichier "* .C" pour mieux comprendre la différence. Dans le même projet, nous ajoutons le fichier suivant:

//--------------------------------------
//Filename: "my_big_project_splitted.C"
#include "my_project.H"

void old_main_test(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 5678;

int function_1(void) return 12;
int function_3(void) return 34;

Caractéristiques importantes à noter:

  • Lorsqu'une fonction est définie comme "statique" dans un fichier d'en-tête, le compilateur / éditeur de liens doit trouver une instance d'une fonction portant ce nom dans chaque module qui utilise ce fichier d'inclusion.

  • Une fonction qui fait partie de la bibliothèque C peut être remplacée dans un seul module en redéfinissant un prototype avec "statique" uniquement dans ce module. Par exemple, remplacez tout appel à «malloc» et «free» pour ajouter une fonction de détection de fuite de mémoire.

  • Le spécificateur "extern" n'est pas vraiment nécessaire pour les fonctions. Lorsque "statique" n'est pas trouvé, une fonction est toujours supposée être "externe".

  • Cependant, "extern" n'est pas la valeur par défaut pour les variables. Normalement, tout fichier d'en-tête qui définit les variables pour être visible sur de nombreux modules doit utiliser "extern". La seule exception serait si un fichier d'en-tête est garanti d'être inclus à partir d'un et d'un seul module.

    De nombreux chefs de projet exigeraient alors que cette variable soit placée au début du module, et non dans un fichier d'en-tête. Certains grands projets, comme l'émulateur de jeu vidéo "Mame", exigent même que cette variable n'apparaisse qu'au-dessus de la première fonction qui les utilise.


Alors, pourquoi une fonction statique a-t-elle besoin d'une définition par rapport aux fonctions externes? (Je sais que c'est 2 ans de retard, mais c'est vraiment très utile pour comprendre)
SubLock69

2
La définition est nécessaire si vous appelez la fonction à la ligne 100 et l'instanciez à la ligne 500. La ligne 100 déclarerait un prototype non défini. Donc, vous ajoutez le prototype vers le haut.
Christian Gingras

15

En C, 'extern' est implicite pour les prototypes de fonctions, car un prototype déclare une fonction qui est définie ailleurs. En d'autres termes, un prototype de fonction a une liaison externe par défaut; utiliser «extern» est bien, mais est redondant.

(Si une liaison statique est requise, la fonction doit être déclarée comme "statique" à la fois dans son prototype et son en-tête de fonction, et ceux-ci doivent normalement être tous deux dans le même fichier .c).


8

Un très bon article que je suis venu sur le externmot - clé, avec les exemples: http://www.geeksforgeeks.org/understanding-extern-keyword-in-c/

Bien que je ne convienne pas que l'utilisation externde déclarations de fonction soit redondante. Ceci est censé être un paramètre de compilation. Je recommande donc d'utiliser les externdéclarations de fonction lorsque cela est nécessaire.


3
J'ai lu l'article geeksforgeeks.org avant de venir ici, mais je l'ai trouvé assez mal écrit. Outre les lacunes grammaticales et syntaxiques, il utilise beaucoup de mots pour faire le même point plusieurs fois, puis efface les informations critiques. Par exemple, dans l'exemple 4, tout à coup, 'somefile.h' est inclus, mais rien n'est dit à ce sujet autre que: "Supposons que somefile.h ait la définition de var". Eh bien, les informations que nous "supposons" se trouvent être celles que je recherche. Malheureusement, aucune des réponses sur cette page n'est meilleure.
Elise van Looij

6

Si chaque fichier de votre programme est d'abord compilé dans un fichier objet, alors les fichiers objet sont liés ensemble, vous en avez besoin extern. Il dit au compilateur "Cette fonction existe, mais le code pour elle est ailleurs. Ne paniquez pas."


C'est comme ça que la traduction se fait normalement: les fichiers source se compilent en fichiers objets, puis sont liés. Quand n'auriez-vous pas besoin d'un extern dans ce cas? Vous n'utiliseriez pas non plus #include pour obtenir des fonctions, mais plutôt des prototypes de fonctions. Je ne comprends pas de quoi tu parles.
David Thornley

Il me semble avoir récemment ce problème de lecture erronée. Désolé pour ça. Quand j'étais nouveau dans C, j'incluais "fichier.c" pour simplement inclure les fonctions dans un fichier directement dans l'autre fichier. Ensuite, j'ai compris comment utiliser «extern». Je pensais qu'il faisait la même erreur que moi.
Chris Lutz

4

Toutes les déclarations de fonctions et de variables dans les fichiers d'en-tête doivent être extern.

Les exceptions à cette règle sont les fonctions en ligne définies dans l'en-tête et les variables qui - bien que définies dans l'en-tête - devront être locales à l'unité de traduction (le fichier source dans lequel l'en-tête est inclus): elles devraient l'être static.

Dans les fichiers source, externne doit pas être utilisé pour les fonctions et variables définies dans le fichier. Juste préfixez les définitions locales avec staticet ne faites rien pour les définitions partagées - ce seront des symboles externes par défaut.

La seule raison à utiliser externdans un fichier source est de déclarer des fonctions et des variables qui sont définies dans d'autres fichiers source et pour lesquelles aucun fichier d'en-tête n'est fourni.


La déclaration de prototypes de fonction externn'est en fait pas nécessaire. Certaines personnes ne l'aiment pas, car cela ne fera que gaspiller de l'espace et les déclarations de fonction ont déjà tendance à déborder des limites de ligne. D'autres l'aiment parce que de cette façon, les fonctions et les variables peuvent être traitées de la même manière.


Pouvez-vous expliquer pourquoi "Toutes les déclarations de fonctions et de variables dans les fichiers d'en-tête doivent être externes."? Il me semble, d'après les autres réponses, qu'ils sont externes par défaut.
lillq

@Lane: externest facultatif pour les déclarations de fonctions, mais j'aime traiter les variables et les fonctions de la même manière - au moins c'est la chose la plus raisonnable que j'ai pu trouver, car je ne me souviens pas exactement pourquoi j'ai commencé à faire cela;)
Christoph

N'est-ce pas une meilleure idée de toujours inclure des variables globales dans le fichier C afin qu'elles ne soient pas vues par d'autres fichiers C aléatoires qui incluent l'en-tête. Et pour toujours utiliser extern sur chaque global sauf le vrai puits initialisé par souci de clarté; s'il est préfixé extern, il est défini ailleurs.
ste3e

3

Les fonctions réellement définies dans d'autres fichiers source ne doivent être déclarées que dans les en-têtes. Dans ce cas, vous devez utiliser extern lors de la déclaration du prototype dans un en-tête.

La plupart du temps, vos fonctions seront l'une des suivantes (plus comme une meilleure pratique):

  • statique (fonctions normales qui ne sont pas visibles en dehors de ce fichier .c)
  • statique en ligne (en ligne à partir de fichiers .c ou .h)
  • extern (déclaration dans les en-têtes du type suivant (voir ci-dessous))
  • [aucun mot-clé que ce soit] (fonctions normales destinées à être accessibles à l'aide de déclarations externes)

Pourquoi voudriez-vous externaliser lors de la déclaration du prototype si c'est la valeur par défaut?
lillq

@Lane: Peut-être un peu biaisé, mais chaque projet sain sur lequel j'ai travaillé utilise la convention suivante: dans les en-têtes, déclarez les prototypes uniquement pour les fonctions externes (donc extern). Dans les fichiers .c, des prototypes simples peuvent être utilisés pour éviter d'avoir à passer une commande spécifique, mais ils ne doivent pas être placés dans des en-têtes.
Eduard - Gabriel Munteanu

1

Lorsque vous avez cette fonction définie sur une autre DLL ou bibliothèque, de sorte que le compilateur se reporte à l'éditeur de liens pour le trouver. Le cas typique est lorsque vous appelez des fonctions à partir de l'API OS.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.