Effets du mot-clé extern sur les fonctions C


172

En C, je n'ai remarqué aucun effet du externmot clé utilisé avant la déclaration de fonction. Au début, je pensais que la définition extern int f();dans un seul fichier vous obligeait à 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;}

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

Y a-t-il des effets à utiliser extern avant les définitions de fonction ? 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 standards ont choisi de joncher la grammaire de mots-clés superflus.

EDIT: Pour clarifier les choses, je sais qu'il ya une utilisation pour externdes variables, mais je demande seulement à propos externde fonctions .


Selon certaines recherches que j'ai faites en essayant d'utiliser ceci à des fins de création de modèles fous, extern n'est pas pris en charge sous la forme prévue par la plupart des compilateurs, et ne fait donc vraiment rien.
Ed James

4
Ce n'est pas toujours superflu, voyez ma réponse. Chaque fois que vous avez besoin de partager quelque chose entre des modules que vous ne voulez PAS dans un en-tête public, c'est très utile. Cependant, «externaliser» chaque fonction dans un en-tête public (avec des compilateurs modernes) a très peu ou pas d'avantages, car ils peuvent le comprendre par eux-mêmes.
Tim Post

@Ed .. si volatile int foo est un global dans foo.c et que bar.c en a besoin, bar.c doit le déclarer comme extern. Il a ses avantages. Au-delà de cela, vous devrez peut-être partager une fonction que vous ne souhaitez PAS exposer dans un en-tête public.
Tim Post


2
@Barry Si pas du tout, l'autre question est un double de celle-ci. 2009 vs 2012
Elazar Leibovich

Réponses:


138

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 voir, 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é dans foo.c lorsqu'il est lié, et foo.c a besoin d'une fonction de bar.c lorsqu'il est lié.

En utilisant «extern», vous dites au compilateur que tout ce qui suit sera trouvé (non statique) au moment de la liaison; ne lui réservez rien dans la passe actuelle 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 du global entre les modules et que vous ne voulez pas le mettre / initialiser dans un en-tête.

Techniquement, chaque fonction dans un en-tête public de bibliothèque est «extern», mais leur étiquetage en tant que tel a très peu ou pas d'avantages, selon le compilateur. La plupart des compilateurs peuvent comprendre cela par eux-mêmes. Comme vous le voyez, ces fonctions sont en fait 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 reviendra pas dans cet exemple (puisqu'il ne s'agit que d'un exemple simple). Imaginez simplement que stop_now soit modifié lorsqu'un signal est desservi (donc volatile) si cela ne semble pas assez pratique.

Les externes sont très utiles pour des choses comme les gestionnaires de signaux, un mutex que vous ne voulez pas mettre dans un en-tête ou une structure, etc. La plupart des compilateurs optimiseront pour s'assurer qu'ils ne réservent aucune mémoire pour des objets externes, car ils savent 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 cela pourra aider :)


56
Votre code se compilera très bien sans l'externe avant la fonction bar_function.
Elazar Leibovich

2
@Tim: Alors vous n'avez pas eu le privilège douteux de travailler avec le code avec lequel je travaille. Cela peut arriver. Parfois, l'en-tête contient également la définition de la fonction statique. C'est moche et inutile 99,99% du temps (je pourrais être hors d'un ordre ou deux ou d'une ampleur, en exagérant à quelle fréquence c'est nécessaire). Cela se produit généralement lorsque les gens comprennent mal qu'un en-tête n'est nécessaire que lorsque d'autres fichiers source utiliseront les informations; l'en-tête est (ab) utilisé pour stocker les informations de déclaration pour un fichier source et aucun autre fichier ne devrait l'inclure. Parfois, cela se produit pour des raisons plus déformées.
Jonathan Leffler

2
@Jonathan Leffler - En effet douteux! J'ai déjà hérité d'un code assez sommaire, mais je peux honnêtement dire que je n'ai jamais vu quelqu'un mettre une déclaration statique dans un en-tête. On dirait que vous avez un travail plutôt amusant et intéressant, cependant :)
Tim Post

1
L'inconvénient du 'prototype de fonction pas dans l'en-tête' est que vous n'obtenez pas la vérification automatique indépendante de la cohérence entre la définition de la fonction dans bar.cet la déclaration dans foo.c. Si la fonction est déclarée dans foo.h et que les deux fichiers incluent foo.h, l'en-tête assure la cohérence entre les deux fichiers source. Sans elle, si la définition de bar_functionin bar.cchange mais que la déclaration in foo.cn'est pas modifiée, alors les choses tournent mal au moment de l'exécution; le compilateur ne peut pas repérer le problème. Avec un en-tête correctement utilisé, le compilateur repère le problème.
Jonathan Leffler

1
extern sur les déclarations de fonctions est superflu comme «int» dans «unsigned int». C'est une bonne pratique d'utiliser 'extern' quand le prototype n'est PAS une déclaration avant ... Mais il devrait vraiment vivre dans un en-tête sans 'extern' à moins que la tâche ne soit un cas de bord. stackoverflow.com/questions/10137037/…

82

Pour autant que je me souvienne de la norme, toutes les déclarations de fonctions sont considérées comme "extern" par défaut, il n'est donc pas nécessaire de le spécifier explicitement.

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


21
Ensuite, en tant que concepteur standard, je vais interdire l' utilisation d'extern avec des fonctions, car cela ajoute simplement du bruit à la grammaire.
Elazar Leibovich

3
La rétrocompatibilité peut être pénible.
MathuSum Mut

1
@ElazarLeibovich En fait, dans ce cas particulier, l' interdire est ce qui ajouterait du bruit à la grammaire.
Courses de légèreté en orbite le

1
La limitation d'un mot-clé ajoute du bruit me dépasse, mais je suppose que c'est une question de goût.
Elazar Leibovich

Cependant, il est utile d'autoriser l'utilisation de "extern" pour les fonctions, car cela indique aux autres programmeurs que la fonction est définie dans un autre fichier, pas dans le fichier courant et n'est pas non plus déclarée dans l'un des en-têtes inclus.
DimP

23

Vous devez distinguer deux concepts distincts: la définition de fonction et la déclaration de symbole. "extern" est un modificateur de lien, un indice au compilateur sur l'endroit où le symbole auquel il est fait référence par la suite est défini (l'indice est "pas ici").

Si j'écris

extern int i;

dans la portée du fichier (en dehors d'un bloc fonctionnel) dans un fichier C, alors vous dites "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. La définition dans ce cas l'emporte sur l'externe.

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

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

L'utilisation de externest 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 externest utile si vous souhaitez explicitement éviter la définition d'une variable.

Laisse-moi expliquer:

Disons que le fichier ac contient:

#include "a.h"

int i = 2;

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

Le fichier ah comprend:

extern int i;
int f(void);

et le fichier bc contient:

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

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

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

Les variables d'étendue de fichier sont implicitement définies avec une valeur par défaut de 0 ou NULL si vous ne leur attribuez pas explicitement de valeur, contrairement aux variables d'étendue 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 fonctions, le mot clé est en effet redondant. Les déclarations de fonction n'ont pas de définition implicite.


Vouliez-vous supprimer la int i = 2ligne du troisième paragraphe? Et est-il correct de déclarer, vu que int i;le compilateur allouera de la mémoire pour cette variable, mais que vu extern int i;, le compilateur n'allouera PAS de mémoire mais recherchera la variable ailleurs?
Frozen Flame

En fait, si vous omettez le mot-clé "extern", le programme ne se compilera pas à cause de la redéfinition de i dans ac et bc (à cause de ah).
Nixt

15

Le externmot-clé prend différentes formes selon l'environnement. Si une déclaration est disponible, le externmot clé prend le lien comme celui spécifié précédemment dans l'unité de traduction. En l'absence d'une telle déclaration, externspécifie le 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 C99 (n1256):

6.2.2 Liens d'identifiants

[...]

4 Pour un identifiant déclaré avec le spécificateur de classe de stockage extern dans un périmètre dans lequel une déclaration préalable de cet identifiant est visible, 23) si la déclaration antérieure spécifie un lien interne ou externe, le lien de l'identifiant lors de la déclaration ultérieure est le même comme le lien spécifié lors de la déclaration préalable. Si aucune déclaration préalable n'est visible, ou si la déclaration antérieure ne spécifie aucun lien, alors l'identifiant a 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 identificateur pour un objet a une portée de fichier et aucun spécificateur de classe de stockage, son lien est externe.


Est-ce la norme, ou me dites-vous simplement le comportement d'un compilateur typique? Dans le cas de la norme, je serai heureux d'avoir un lien vers la norme. Mais merci!
Elazar Leibovich

C'est le comportement standard. Le projet C99 est disponible ici: < open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf >. La norme actuelle n'est cependant pas gratuite (le projet est assez bon pour la plupart des besoins).
dirkgently

1
Je viens de le tester dans gcc et c'est à la fois "extern int h (); static int h () {return 0;}" et "int h (); static int h () {return 0;}" sont acceptés avec le même avertissement. Est-ce uniquement C99 et non ANSI? Pouvez-vous me référer à la section exacte du brouillon, car cela ne semble pas être le cas pour gcc.
Elazar Leibovich

Revérifier. J'ai essayé la même chose avec gcc 4.0.1 et j'obtiens une erreur là où elle devrait être. Essayez également le compilateur en ligne de comeau ou codepad.org si vous n'avez pas accès à d'autres compilateurs. Lisez la norme.
dirkgently

2
@dirkgently, Ma vraie question est est-ce qu'il y a un effet pour utiliser exetrn avec la déclaration de fonction, et s'il n'y en a pas, pourquoi est-il possible d'ajouter extern à une déclaration de fonction. Et la réponse est non, il n'y a pas d'effet, et il y avait autrefois un effet avec des compilateurs pas si standard.
Elazar Leibovich

11

Les fonctions en ligne ont des règles spéciales sur ce que externsignifie. (Notez que les fonctions en ligne sont une extension C99 ou GNU; elles n'étaient pas dans le C.

Pour les fonctions non en ligne, externn'est pas nécessaire car il est activé par défaut.

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


C'est la seule réponse ici que les deux sont correctes et répondent réellement à la question.
robinjam

4

IOW, extern est redondant et ne fait rien.

C'est pourquoi, 10 ans plus tard:

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

*.[ch]: supprimer externdes déclarations de fonction en utilisantspatch

Il y a eu une poussée pour supprimer externdes déclarations de fonction.

Supprimez certaines instances de " extern" pour les déclarations de fonction qui sont interceptées par Coccinelle.
Notez que Coccinelle a quelques difficultés à traiter les fonctions avec __attribute__ou varargs, donc certaines externdéclarations sont laissées pour compte dans un futur patch.

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

Ce n'est cependant pas toujours simple:

Voir commit 7027f50 (04 sept. 2019) par Denton Liu ( Denton-L) .
(Fusionné par Denton Liu - Denton-L- dans commit 7027f50 , 05 sept. 2019)

compat/*.[ch]: supprimer externdes déclarations de fonction à l'aide de spatch

Dans 5545442 ( *.[ch]: supprimer externdes déclarations de fonction à l'aide de spatch, 2019-04-29, Git v2.22.0-rc0), nous avons supprimé les externs des déclarations de fonction en utilisant spatchmais nous avons intentionnellement exclu les fichiers sous compat/car certains sont directement copiés depuis un amont et nous devrions éviter les barattant afin que la fusion manuelle des futures mises à jour soit plus simple.

Dans le dernier commit, nous avons déterminé les fichiers provenant d'un amont afin de pouvoir les exclure et les exécuter spatchsur le reste.

C'était le patch Coccinelle utilisé:

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

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

$ git ls-files compat/\*\*.{c,h} |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Coccinelle a du mal à gérer __attribute__et varargs, nous avons donc exécuté ce qui suit pour nous assurer qu'aucune modification restante n'a été laissée pour compte:

$ git ls-files compat/\*\*.{c,h} |
    xargs sed -i'' -e 's/^\(\s*\)extern \([^(]*([^*]\)/\1\2/'
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Notez qu'avec Git 2.24 (Q4 2019), tout faux externest supprimé.

Voir commit 65904b8 (30 septembre 2019) par Emily Shaffer ( nasamuffin) .
Aide: Jeff King ( peff) .
Voir commit 8464f94 (21 sept. 2019) par Denton Liu ( Denton-L) .
Aide: Jeff King ( peff) .
(Fusionné par Junio ​​C Hamano - gitster- dans commit 59b19bc , 07 octobre 2019)

promisor-remote.h: suppression externde la déclaration de fonction

Lors de la création de ce fichier, chaque fois qu'une nouvelle déclaration de fonction était introduite, elle incluait un fichier extern.
Cependant, à partir de 5545442 ( *.[ch]: supprimer externdes déclarations de fonction en utilisant spatch, 2019-04-29, Git v2.22.0-rc0), nous avons activement essayé d'empêcher l'utilisation d'externs dans les déclarations de fonctions car elles sont inutiles.

Supprimer ces faux externs.


3

Le externmot clé informe le compilateur que la fonction ou la variable a un lien externe - en d'autres termes, qu'elle est visible à partir de fichiers autres que celui dans lequel elle est définie. En ce sens, il a la signification opposée au staticmot - clé. C'est un peu bizarre à mettre externau moment de la définition, car aucun autre fichier n'aurait de visibilité sur la définition (ou cela entraînerait plusieurs définitions). Normalement, vous insérez externune déclaration à un moment donné avec une visibilité externe (comme un fichier d'en-tête) et mettez la définition ailleurs.


2

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 régulières, qui ne sont pas déclarées extern, elle peut être définie dans n'importe quel fichier source (mais pas dans plusieurs fichiers source, sinon vous obtiendrez une erreur de l'éditeur de liens indiquant que vous avez donné plusieurs définitions de la fonction), y compris celle dans dont il est déclaré extern. Ainsi, dans votre cas, l'éditeur de liens résout la définition de la fonction dans le même fichier.

Je ne pense pas que cela serait très utile, mais faire de telles expériences donne un meilleur aperçu du fonctionnement du compilateur et de l'éditeur de liens du langage.


2
IOW, extern est redondant et ne fait rien. Ce serait beaucoup plus clair si vous l'exprimiez ainsi.
Elazar Leibovich du

@ElazarLeibovich Je viens de rencontrer un cas similaire dans notre base de code et j'ai eu la même conclusion. Toutes ces réponses à travers ici peuvent être résumées dans votre seule doublure. Cela n'a aucun effet pratique mais peut être agréable pour la lisibilité. Ravi de vous voir en ligne et pas seulement en meetups :)
Aviv

1

La raison pour laquelle cela n'a aucun effet est qu'au moment de la liaison, l'éditeur de liens tente de résoudre la définition externe (dans votre cas extern int f()). Peu importe qu'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
Alors pourquoi permettre d'ajouter externà n'importe quelle fonction?
Elazar Leibovich

2
Veuillez vous abstenir de placer du spam sans rapport avec vos messages. Merci!
Mac
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.