web-dev-qa-db-fra.com

Effets du mot clé extern sur les fonctions C

En C, je n'ai remarqué aucun effet du mot clé extern utilisé avant la déclaration de fonction. Au début, je pensais que lors de la définition de extern int f(); dans un seul fichier oblige à l'implémenter en dehors de la portée du fichier. Cependant, j'ai découvert que les deux:

extern int f();
int f() {return 0;}

et

extern int f() {return 0;}

compiler très bien, sans les avertissements de gcc. J'ai utilisé gcc -Wall -ansi; il n'accepterait même pas les commentaires //.

Existe-t-il des effets pour l'utilisation de externavant les définitions de fonctions? Ou est-ce juste un mot-clé facultatif sans effets secondaires pour les fonctions.

Dans ce dernier cas, je ne comprends pas pourquoi les concepteurs standard ont choisi de surcharger la grammaire avec des mots-clés superflus.

EDIT: Pour clarifier, je sais qu'il existe un usage pour extern dans les variables, mais je ne parle que de extern dans fonctions.

161
Elazar Leibovich

Nous avons deux fichiers, foo.c et bar.c.

Voici foo.c

#include <stdio.h>

volatile unsigned int stop_now = 0;
extern void bar_function(void);

int main(void)
{
  while (1) {
     bar_function();
     stop_now = 1;
  }
  return 0;
}

Maintenant, voici bar.c

#include <stdio.h>

extern volatile unsigned int stop_now;

void bar_function(void)
{
   while (! stop_now) {
      printf("Hello, world!\n");
      sleep(30);
   }
}

Comme vous pouvez le constater, nous n'avons pas d'en-tête partagé entre foo.c et bar.c, cependant bar.c a besoin de quelque chose déclarée dans foo.c quand il est lié et foo.c a besoin d'une fonction de bar.c quand il est lié.

En utilisant 'extern', vous dites au compilateur que tout ce qui suit sera trouvé (non statique) au moment du lien; ne lui réservez rien dans le courant passe car il sera rencontré plus tard. Les fonctions et les variables sont traitées de la même manière à cet égard.

C'est très utile si vous avez besoin de partager des modules globaux et que vous ne voulez pas le mettre/l'initialiser dans un en-tête.

Techniquement, chaque fonction d'un en-tête public de bibliothèque est "extern". Cependant, leur étiquetage en tant que tel présente peu d'avantages, selon le compilateur. La plupart des compilateurs peuvent le comprendre par eux-mêmes. Comme vous le voyez, ces fonctions sont en réalité définies ailleurs.

Dans l'exemple ci-dessus, main () afficherait hello world une seule fois, mais continuerait à entrer bar_function (). Notez également que bar_function () ne retournera pas dans cet exemple (puisqu'il ne s'agit que d'un exemple simple). Imaginez juste que stop_now soit modifié lorsqu'un signal est traité (donc volatil) si cela ne semble pas assez pratique.

Les externes sont très utiles pour des choses comme les gestionnaires de signaux, les mutex que vous ne voulez pas mettre dans un en-tête ou une structure, etc. La plupart des compilateurs optimisent pour s'assurer qu'ils ne réservent pas de mémoire pour des objets externes, car ils savent qu'ils Je vais le réserver dans le module où l'objet est défini. Cependant, encore une fois, il est inutile de le spécifier avec des compilateurs modernes lors du prototypage de fonctions publiques.

J'espère que ça t'as aidé :)

130
Tim Post

Autant que je me souvienne de la norme, toutes les déclarations de fonction sont considérées par défaut comme "externes". Il n'est donc pas nécessaire de la spécifier explicitement.

Cela ne rend pas ce mot clé inutile puisqu'il peut également être utilisé avec des variables (et c'est le cas - c'est la seule solution pour résoudre les problèmes de couplage). Mais avec les fonctions - oui, c'est optionnel.

76
Rageous

Vous devez distinguer deux concepts distincts: la définition de fonction et la déclaration de symbole. "extern" est un modificateur de liaison, une indication pour le compilateur indiquant où le symbole mentionné ultérieurement est défini (la suggestion est, "pas ici").

Si j'écris

extern int i;

dans l'étendue du fichier (en dehors d'un bloc de fonction) dans un fichier C, vous dites alors "la variable peut être définie ailleurs".

extern int f() {return 0;}

est à la fois une déclaration de la fonction f et une définition de la fonction f. Dans ce cas, la définition remplace l'extern.

extern int f();
int f() {return 0;}

est d'abord une déclaration, suivie de la définition.

L'utilisation de extern est incorrecte si vous souhaitez déclarer et définir simultanément une variable d'étendue de fichier. Par exemple,

extern int i = 4;

donnera une erreur ou un avertissement, selon le compilateur.

L'utilisation de extern est utile si vous souhaitez explicitement éviter la définition d'une variable.

Laissez-moi expliquer:

Disons que le fichier a.c contient:

#include "a.h"

int i = 2;

int f() { i++; return i;}

Le fichier a.h comprend:

extern int i;
int f(void);

et le fichier b.c contient:

#include <stdio.h>
#include "a.h"

int main(void){
    printf("%d\n", f());
    return 0;
}

L'extérieur de l'en-tête est utile car il indique au compilateur pendant la phase de liaison que "ceci est une déclaration et non une définition". Si je supprime la ligne dans a.c qui définit i, lui alloue de l'espace et lui attribue une valeur, le programme ne pourra pas être compilé avec une référence non définie. Cela indique au développeur qu'il a fait référence à une variable, mais qu'il ne l'a pas encore définie. Si, en revanche, le mot-clé "extern" est omis et la ligne int i = 2 supprimée, le programme compile toujours - i sera défini avec une valeur par défaut de 0.

Les variables de portée de fichier sont implicitement définies avec une valeur par défaut de 0 ou NULL si vous ne leur affectez pas explicitement une valeur, contrairement aux variables de portée de bloc que vous déclarez en haut d'une fonction. Le mot clé extern évite cette définition implicite et permet ainsi d'éviter les erreurs.

Pour les fonctions, dans les déclarations de fonction, le mot clé est en effet redondant. Les déclarations de fonction n'ont pas de définition implicite.

21
Dave Neary

Le mot clé extern prend différentes formes en fonction de l'environnement. Si une déclaration est disponible, le mot clé extern prend la liaison comme celle spécifiée précédemment dans l'unité de traduction. En l'absence d'une telle déclaration, extern spécifie un lien externe.

static int g();
extern int g(); /* g has internal linkage */

extern int j(); /* j has tentative external linkage */

extern int h();
static int h(); /* error */

Voici les paragraphes pertinents du projet de C99 (n1256):

6.2.2 Liens d'identificateurs

[...]

4 Pour un identifiant déclaré avec le spécificateur de classe de stockage extern dans une étendue dans laquelle une déclaration antérieure de cet identifiant est visible, 23) si la déclaration antérieure spécifie un lien interne ou externe, le couplage de l'identifiant lors de la déclaration ultérieure est identique au lien spécifié dans la déclaration précédente. Si aucune déclaration préalable n'est visible ou si la déclaration antérieure ne spécifie aucun lien, l'identificateur dispose d'un lien externe.

5 Si la déclaration d'un identifiant pour une fonction n'a pas de spécificateur de classe de stockage, son lien est déterminé exactement comme s'il avait été déclaré avec le spécificateur de classe de stockage extern. Si la déclaration d'un identifiant pour un objet a une portée de fichier et aucun spécificateur de classe de stockage, sa liaison est externe.

14
dirkgently

Les fonctions en ligne ont règles spéciales à propos de ce que extern signifie. (Notez que les fonctions en ligne sont une extension C99 ou GNU; elles n'étaient pas dans le C. d'origine.

Pour les fonctions non inline, extern n'est pas nécessaire car activé par défaut.

Notez que les règles pour C++ sont différentes. Par exemple, extern "C" est nécessaire dans la déclaration C++ des fonctions C que vous allez appeler à partir de C++, et il existe différentes règles relatives à inline.

8
user9876

IOW, extern est redondant et ne fait rien.

Voilà pourquoi, 10 ans plus tard:

Voir commit ad6dad0 , commit b199d71 , commettre 5545442 (29 avril 2019) de Denton Liu (_Denton-L_) .
(Fusionné par Junio ​​C Hamano - gitster - dans commettez 4aeeef3 13 mai 2019)

_*.[ch]_: supprime extern des déclarations de fonction à l'aide de spatch

Il y a eu un Push pour supprimer extern des déclarations de fonction.

Supprimez certaines instances de "extern" pour les déclarations de fonction capturées par Coccinelle.
Notez que Coccinelle a quelques difficultés avec le traitement des fonctions avec ___attribute___ ou varargs, de sorte que certaines déclarations extern sont laissées pour être traitées dans un futur correctif.

C'était le patch Coccinelle utilisé:

_  @@
    type T;
    identifier f;
    @@
    - extern
    T f(...);
_

et il a été exécuté avec:

_  $ git ls-files \*.{c,h} |
    grep -v ^compat/ |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place
_
3
VonC

Le mot-clé extern informe le compilateur que la fonction ou la variable a un lien externe, autrement dit qu'elle est visible à partir de fichiers autres que celui dans lequel elle est définie. En ce sens, il a le sens opposé au mot clé static. Il est un peu bizarre de mettre extern au moment de la définition, car aucun autre fichier n'aurait de visibilité sur la définition (sinon cela donnerait plusieurs définitions). Normalement, vous mettez extern dans une déclaration à un moment donné avec une visibilité externe (comme un fichier d’en-tête) et vous placez la définition ailleurs.

3
1800 INFORMATION

déclarer une fonction extern signifie que sa définition sera résolue au moment de la liaison, pas lors de la compilation.

Contrairement aux fonctions standard, qui ne sont pas déclarées extern, il peut être défini dans n’importe quel fichier source (mais pas dans plusieurs fichiers source, sinon vous obtiendrez une erreur de l'éditeur de liens vous indiquant que vous avez donné plusieurs définitions de la fonction), y compris celui de dont il est déclaré extern.So, dans le cas où l’éditeur de liens résolve la définition de la fonction dans le même fichier.

Je ne pense pas que cela soit utile mais faire de telles expériences donne une meilleure idée du fonctionnement du compilateur et de l'éditeur de liens du langage.

2
Rampal Chaudhary

Cela n'a pas d'effet, car l'éditeur de liens essaie de résoudre la définition extern au moment de la création du lien (dans votre cas, extern int f()). Peu importe s'il le trouve dans le même fichier ou dans un fichier différent, tant qu'il est trouvé.

J'espère que ça répond à ta question.

1
Umer