Pourquoi avons-nous besoin du mot-clé ‘extern’ en C si les déclarations d’étendue de fichier ont un lien externe par défaut?

Autant que je sache, toute déclaration d’une variable ou d’une fonction dans la scope du fichier a un lien externe par défaut . static signifie “il a un lien interne”, extern – “il peut être défini ailleurs”, pas “il a un lien externe”.

Si oui, pourquoi avons-nous besoin d’ extern mot-clé extern ? En d’autres termes, quelle est la différence entre int foo; et extern int foo; (scope du fichier)?

Le mot-clé extern est principalement utilisé pour les déclarations de variables. Lorsque vous transmettez-déclarez une fonction, le mot-clé est facultatif.

Le mot-clé permet au compilateur de distinguer une déclaration directe d’une variable globale de la définition d’une variable:

 extern double xyz; // Declares xyz without defining it 

Si vous conservez cette déclaration seule et que vous utilisez ensuite xyz dans votre code, vous déclencherez une erreur “symbole non défini” pendant la phase de liaison.

 double xyz; // Declares and defines xyz 

Si vous conservez cette déclaration dans un fichier d’en-tête et utilisez-la à partir de plusieurs fichiers C / C ++, vous déclencherez une erreur “définitions multiples” pendant la phase de liaison.

La solution consiste à utiliser extern dans l’en-tête et non à extern dans exactement un fichier C ou C ++.

A titre d’illustration, comstackz le programme suivant: (en utilisant cc -c program.c, ou l’équivalent)

 extern char bogus[0x12345678] ; 

Maintenant, supprimez le mot clé “extern” et comstackz à nouveau:

 char bogus[0x12345678] ="1"; 

Exécutez objdump (ou l’équivalent) sur les deux objects.

Vous constaterez que sans le mot-clé extern, l’ espace est réellement alloué.

  • Avec le mot-clé extern tout ce qui est “bidon” n’est qu’une référence. Vous dites au compilateur: “il doit y avoir un personnage char bogus[xxx] quelque part, corrigez-le!”
  • Sans le mot-clé extern, vous dites: “J’ai besoin d’espace pour un caractère variable char bogus[xxx] , donnez-moi cet espace!”

Le problème est que l’allocation réelle de mémoire pour un object est rescope au temps du lien: le compilateur ajoute simplement un enregistrement à l’object, informant l’éditeur de liens qu’un object devrait (ou ne devrait pas) être atsortingbué. Dans tous les cas, le compilateur au moins appenda le nom (et la taille) de l’object, afin que l’éditeur de liens / chargeur puisse le réparer.

C99

Je vais répéter ce que d’autres ont dit, mais en citant et en interprétant le projet de C99 N1256 .

Tout d’abord, je confirme votre affirmation selon laquelle le lien externe est la valeur par défaut pour la scope du fichier 6.2.2 / 5 “Liens des identificateurs” :

Si la déclaration d’un identifiant pour un object a une scope de fichier et aucun spécificateur de classe de stockage, sa liaison est externe.

Le sharepoint confusion est extern ne modifie pas seulement le lien, mais aussi si une déclaration d’object est une définition ou non. Cela est important car 6.9 / 5 “Définitions externes” dit qu’il ne peut y avoir qu’une seule définition externe:

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 object. 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 dont le résultat est une constante entière), il doit exister une définition externe pour l’identificateur dans le programme entier. sinon, il n’y en aura pas plus d’un.

où “définition externe” est défini par l’extrait de grammaire:

 translation-unit: external-declaration 

cela signifie donc une déclaration de niveau supérieur “fichier”.

Ensuite, 6.9.2 / 2 “Définitions d’objects externes” dit (object signifie “données d’une variable”):

La déclaration d’un identifiant pour un object ayant une scope de fichier sans initialiseur, et sans spécificateur de classe de stockage ou avec le spécificateur de classe de stockage static, constitue une définition provisoire. Si une unité de traduction contient une ou plusieurs définitions provisoires pour un identificateur et que l’unité de traduction ne contient aucune définition externe pour cet identificateur, le comportement est exactement comme si l’unité de traduction contenait une déclaration de la scope du fichier de cet identificateur, avec le type composite as de la fin de l’unité de traduction, avec un initialiseur égal à 0.

Alors:

 extern int i; 

n’est pas une définition, car il possède un spécificateur de classe de stockage: extern .

Toutefois:

 int i; 

n’a pas de spécificateur de classe de stockage, il s’agit donc d’une définition provisoire . Et s’il n’y a plus de déclarations externes pour i , alors nous pouvons append l’initialiseur égal à 0 = 0 implicitement:

 int i = 0; 

Donc, si nous avions plusieurs int i; dans différents fichiers, l’éditeur de liens devrait théoriquement exploser avec plusieurs définitions.

GCC 4.8 n’est toutefois pas conforme et, en tant qu’extension, autorise plusieurs int i; à travers différents fichiers comme mentionné à: https://stackoverflow.com/a/3692486/895245 .

Ceci est implémenté dans ELF avec un symbole commun, et cette extension est si courante qu’elle est mentionnée dans la norme à J.5.11 / 5 Extensions communes> Définitions externes multiples :

Il peut exister plusieurs définitions externes pour l’identifiant d’un object, 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).

Un autre endroit où extern a un effet est dans les déclarations de block-scope, voir: Les variables locales et de registre peuvent-elles être déclarées extern?

S’il existe un initialiseur pour la déclaration d’object, extern n’a aucun effet:

 extern int i = 0; 

équivaut à

 int i = 0; 

Les deux sont des définitions.

extern semble pas avoir d’effet sur les fonctions: effets du mot-clé extern sur les fonctions C, car il n’existe pas de concept analogue de définition provisoire.

Vous ne pouvez définir une variable qu’une seule fois.

Si plusieurs fichiers utilisent la même variable, celle-ci doit être déclarée de manière redondante dans chaque fichier. Si vous faites un simple “int foo;” vous obtiendrez une erreur de définition en double. Utilisez “extern” pour éviter une erreur de définition en double. Extern est comme dire au compilateur “hé, cette variable existe mais ne la créez pas. Elle est définie ailleurs”.

Le processus de construction en C n’est pas “intelligent”. Il ne cherchera pas dans tous les fichiers pour voir si une variable existe. Vous devez explicitement dire que la variable existe dans le fichier actuel, tout en évitant de la créer deux fois.

Même dans le même fichier, le processus de construction n’est pas très intelligent. Il va de haut en bas et ne reconnaîtra pas le nom d’une fonction s’il est défini sous le point d’utilisation. Vous devez donc le déclarer plus haut.