extern tout seul devant le prototype d'une fonction
ne sert a rien (sauf a etre plus explicit que ne le demande
le standard C).
Elle est incluse dans la table des liens par défaut
pour eviter cela, on utilise static.
une fonction sans son corps n'est pas instanciée alors
qu'une variable est instanciée par défaut sauf si on précise extern devant sa déclaration, par contre si on lui assigne une valeur en meme temps qu'on la declare alors elle est instanciée même avec le extern.
C'est un peu compliqué mais c'est la faute au C qui est concu un peu bizarrement; il y a des comportements par défaut ce qui fait que selon qu'on precise ou pas tel modificateur, ca va influer sur un autre comportement (portée du nom, mode d'instanciation).
extern "C", c'est pour éviter le "name mangling".
j'avais fait un petit article a l'époque sur le fonctionnement (basique) d'un compilateur/editeur de lien:
voici le texte.
Etapes d'une compilation d'un projet C++
La compilation d'un programme, lib ou autre en C++ comporte trois etapes. Le preprocessing ou precompilation. La compilation. L'edition des liens. (avec etapes intermediaires suivant le compilateur).
Le preprocessing
La particularité de cette etape est de ne pas generer de binaire mais de simplement generer du source a partir de directives de compilation trouvées dans les fichiers. Cela inclut les macros, les #include et l'instanciation des classes et fonctions templates. (les templates doivent etre traites un peu a part parce que cela peut etre vu comme un vrai metalangage de programmation qui sert a generer du C++).
C'est pour ca qu'on dit que le compilateur n'agit pas sur un fichier mais sur une "translation unit", une unité de traduction qui est le fichier source original ou toutes les macros ont été deployées et tous les fichiers include ont ete inclus. C'est important de comprendre cette notion de "translation unit" pour travailler sur des projets C++ avec plusieurs fichiers.
La compilation
Le compilateur agit sur une "translation unit" qui est un fichier .cpp avec toutes les includes et toutes les macros deployées afin de generer un fichier objet (c'est a dire un fichier binaire mais avec des appels symboliques entre les unités de traduction).
Le compilateur est borgne et ne voit que ce qui concerne sa translation unit. C'est pour ca qu'on doit lui fournir un certain nombre d'indication sur ce qui se trouve dans les autres unités avant que l'edition des liens ne raccroche tous les bouts ensembles.
Ces indications sont des declarations de fonctions sous forme de prototypes ou des declarations de variables "extern".
Qu'est-ce qu'un nom en C++?
un nom est un mot réservé par le programmeur par une declaration. La declaration a toujours lieu en amont de toute reference a ce nom, ainsi le compilateur lorsqu'il tombe sur la reference en question sait a quoi cela se rapporte: variable, fonction ou type (classe, enum etc..).
On a quatre portées principalement pour les noms; bloc, fonction, globale ou prototype.
La plus large c'est la portée globale ou "file scope". Le "file scope" qui devrait etre plus precisemment appelée "translation unit scope" parce qu'elle commence a la premiere declaration du nom et se termine a la fin de l'unité de traduction. C'est le cas pour une variable, fonction ou type declaré en dehors de tout bloc {} ou en dehors d'un declaration de fonction.
Ce qui precede est valable en C.
exemple: dans
void affiche(char * _str);
on a deux declarations de nom. la premiere c'est "affiche" qui est une fonction de portée globale. La seconde c'est "_str" qui est une variable dont la portée est restreinte au prototype (on peut meme s'abstenir de la nommer en vrai).
La resolution des noms lors du linkage(où l'on reparle de l'extern "C" )
Une fois chaque unité compilée, il est nécessaire de recoller les morceaux c'est à dire de remplacer les liaisons symboliques par de vraies liaisons. C'est la que la classe d'enregistrement (storage class) et la defiguration des noms (name mangling ou "substantypage" ) interviennent.
Tout nom de portée global est accessible aux autres unités par défaut ("storage class extern" ). C'est a dire qu'il suffit d'inclure un prototype equivalent pour pouvoir faire appel à cette fonction depuis une autre unité de traduction. Le prototype est une definition de fonction sans corps (sans son code). Pour limiter la portée d'un nom a l'unité courante, il faut utiliser le mot clé "static", ou alors utiliser un espace de nom anonyme ("nameless namespace" ). Les methodes d'une classe, meme static, sont "extern" par defaut (confusant n'est-ce pas
?). Une classe et ses methodes ne peut donc etre dissimulée aux autres unités qu'en la declarant dans un espace de nom anonyme.
Cependant le C++ introduit des nouveautés qui imposent au compilateur d'adjoindre quelques infos supplementaires au nom reel de l'objet avant d'appeler l'editeur de lien.
En C++, il est possible de surcharger une fonction par le nombre et le type des arguments. Pour pouvoir discerner deux fonctions C++ avec le meme nom, il faut donc ajouter la signature de la fonction au nom d'origine. De meme une fonction peut-etre declaré dans un namespace ou etre la methode d'une classe avec des conventions d'appels differentes (this passé implicitement pour une fonction non static).
La consequence de cela c'est que le format des fichiers objet C et des fichiers objet C++ ne sont pas compatibles, un nom defiguré en C++, ne peut pas etre rattaché a une fonction C qui est inscrit tel quel dans le fichier objet; la fonction affiche sera designée "_affiche" dans le fichier objet C, mais "?affiche@@YAXXZ" dans le cas du fichier objet C++.
C'est pour ca aussi qu'il est important que le prototype de la fonction soit exact sur le nombre et le type des arguments passés en C++ (en C c'est moins grave a la compilation mais ca peut planter a l'execution).
Lorsque tu utilises un fichier objet C depuis un programme C++, il faut donc preceder la declaration du nom d'un extern "C" qui indique qu'on revient aux conventions du C et donc qu'on laisse tomber la defiguration. Cela est necessaire aussi pour d'autres langages que le C++ qui ne savent travailler que sur les fichiers objets du C sans defiguration.
Pourquoi il est inutile et dangereux d'utiliser extern "C" dans d'autres cas
Un compilateur sait par avance comment il va defigurer les noms de fonction et de variables et donc saura s'y retrouver dans les objets globaux "extern". Il est donc inutile de faire appel aux conventions "C" dans un meme projet hors librairies exterieures (lib standard ou autres).
La defiguration est necesaire parce qu'elle permet de s'y retrouver dans les fonctions surchargées, les namespaces et autres nouveautés du C++, en utilisant extern "C" tu reviens au plus grand denominateur commun entre le C et le C++.