web-dev-qa-db-fra.com

Comment utiliser correctement le mot clé extern en C

Ma question concerne le moment où une fonction doit être référencée avec le mot clé extern en C.

Je ne vois pas quand cela devrait être utilisé dans la pratique. Au moment d’écrire un programme, toutes les fonctions que j’utilise sont disponibles dans les fichiers d’en-tête que j’ai inclus. Alors, pourquoi serait-il utile de extern d’avoir accès à quelque chose qui n’a pas été exposé dans le fichier d’en-tête?

Je pourrais penser au fonctionnement incorrect de extern et, le cas échéant, corrigez-moi.

Edit: Devriez-vous extern quelque chose quand il s’agit de la déclaration par défaut sans le mot-clé dans un fichier d’en-tête?

221
lillq

"extern" modifie le lien. 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, elle n'instancie pas la variable elle-même, c'est-à-dire n'alloue aucune mémoire. Cela doit être fait ailleurs. Il est donc important d’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é "static" 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.

273
bluebrother

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

À l'aide des réponses et des conversations avec quelques amis, voici l'exemple concret d'une utilisation de extern.

Exemple 1 - pour afficher 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 possède des copies séparées de errno. Ceci est un problème car le même errno est supposé ê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.

188
lillq

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

En ce qui concerne les 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 fichier source unique, sans le mot-clé extern. Le fichier source unique doit être celui qui partage le nom du fichier d’en-tête, pour bonne pratique.

28
aib

En C, "extern" est impliqué pour les prototypes de fonctions, puisqu'un prototype déclare une fonction définie ailleurs. En d'autres termes, un prototype de fonction a un lien 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 dans son en-tête de fonction, qui doivent normalement figurer dans le même fichier .c).

14
Steve Melnikoff

Plusieurs années plus tard, je découvre cette question. Après avoir lu toutes les réponses et tous les commentaires, je pensais pouvoir clarifier quelques détails ... Cela pourrait être utile pour les personnes qui arrivent ici via Google.

La question concerne spécifiquement l'utilisation de fonctions "extern", je vais donc ignorer 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;

Pour compiler et 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 lesquels nous n’avons peut-être pas le code source.

Intégrons à nouveau l'en-tête "my_project.H" dans un fichier "* .C" différent 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 rechercher une instance d'une fonction portant ce nom dans chaque module utilisant 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 tous les appels à "malloc" et "libre" pour ajouter une fonctionnalité de détection des fuites 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 définissant les variables comme étant visibles dans de nombreux modules doit utiliser "extern". La seule exception serait s'il était garanti qu'un fichier d'en-tête soit inclus à partir d'un et d'un seul module.

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

12
Christian Gingras

Un très bon article sur le mot clé extern, ainsi que sur les exemples: http://www.geeksforgeeks.org/understanding-extern-keyword-in-c/

Bien que je ne sois pas d’accord pour dire que l’utilisation de extern dans les déclarations de fonction est redondante. Ceci est supposé être un paramètre du compilateur. Je recommande donc d’utiliser extern dans les déclarations de fonction lorsque cela est nécessaire.

8
tozak

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

5
Chris Lutz

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 inline 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 doivent être static.

Dans les fichiers source, extern ne doit pas être utilisé pour les fonctions et les variables définies dans le fichier. Préfixez simplement les définitions locales avec static et ne faites rien pour les définitions partagées - ce sont des symboles externes par défaut.

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


Déclarer les prototypes de fonction extern est en réalité inutile. Certaines personnes ne l'aiment pas car cela ne fera que gaspiller de l'espace et les déclarations de fonctions ont déjà tendance à dépasser les limites de la ligne. D'autres l'apprécient car, de cette manière, les fonctions et les variables peuvent être traitées de la même manière.

4
Christoph

Lorsque vous avez cette fonction définie sur une dll ou une bibliothèque différente, le compilateur s'en remet à l'éditeur de liens pour la trouver. Le cas typique est lorsque vous appelez des fonctions à partir de l'API du système d'exploitation.

2
Otávio Décio

Les fonctions actuellement définies dans d'autres fichiers sources ne doivent être que déclarées dans les en-têtes. Dans ce cas, vous devez utiliser extern quand déclarer le prototype dans un en-tête.

La plupart du temps, vos fonctions seront l’une des suivantes (plutôt une pratique exemplaire):

  • static (fonctions normales non visibles en dehors de ce fichier .c)
  • inline statique (inlines de fichiers .c ou .h)
  • extern (déclaration dans les en-têtes du type suivant (voir ci-dessous))
  • [pas de mot-clé quel qu'il soit] (fonctions normales accessibles via des déclarations externes)