J'ai une fonction qui est déclarée et définie dans un fichier d'en-tête. C'est un problème en soi. Lorsque cette fonction n'est pas intégrée, chaque unité de traduction qui utilise cet en-tête obtient une copie de la fonction, et lorsqu'elle est liée, elle est dupliquée. J'ai "corrigé" cela en rendant la fonction inline, mais je crains que ce soit une solution fragile car, à ma connaissance, le compilateur ne garantit pas l'inline, même lorsque vous spécifiez le mot clé "inline". Si ce n'est pas vrai, veuillez me corriger.
Quoi qu'il en soit, la vraie question est, qu'advient-il des variables statiques à l'intérieur de cette fonction? Avec combien d'exemplaires dois-je me retrouver?
Je suppose que vous manquez quelque chose, ici.
Déclarer une fonction statique la rendra "cachée" dans son unité de compilation.
Un nom ayant une étendue d'espace de noms (3.3.6) a une liaison interne s'il s'agit du nom de
- une variable, une fonction ou un modèle de fonction qui est explicitement déclaré statique;
3.5/3 - C++ 14 (n3797)
Lorsqu'un nom a un lien interne, l'entité qu'il désigne peut être désignée par des noms provenant d'autres étendues dans la même unité de traduction.
3,5/2 - C++ 14 (n3797)
Si vous déclarez cette fonction statique dans un en-tête, alors toutes les unités de compilation incluant cet en-tête auront leur propre copie de la fonction.
Le fait est que s'il y a des variables statiques à l'intérieur de cette fonction, chaque unité de compilation incluant cet en-tête aura également sa propre version personnelle.
Le déclarer en ligne en fait un candidat pour l'inline (cela ne signifie pas beaucoup de nos jours en C++, car le compilateur sera en ligne ou non, ignorant parfois le fait que le mot clé inline soit présent ou absent):
Une déclaration de fonction (8.3.5, 9.3, 11.3) avec un spécificateur en ligne déclare une fonction en ligne. Le spécificateur en ligne indique à l'implémentation que la substitution en ligne du corps de fonction au point d'appel doit être préférée au mécanisme d'appel de fonction habituel. Une implémentation n'est pas requise pour effectuer cette substitution en ligne au point d'appel; cependant, même si cette substitution en ligne est omise, les autres règles pour les fonctions en ligne définies au 7.1.2 doivent toujours être respectées.
7.1.2/2 - C++ 14 (n3797)
Dans un en-tête, son a un effet secondaire intéressant: la fonction intégrée peut être définie plusieurs fois dans le même module, et l'éditeur de liens les joindra simplement "eux" en un seul (s'ils n'étaient pas intégrés pour la raison du compilateur).
Pour les variables statiques déclarées à l'intérieur, la norme y indique spécifiquement une et une seule d'entre elles:
Une variable locale statique dans une fonction en ligne externe fait toujours référence au même objet.
7.1.2/4 - C++ 98/C++ 14 (n3797)
(les fonctions sont par défaut externes, donc, sauf si vous marquez spécifiquement votre fonction comme statique, cela s'applique à cette fonction)
Cela a l'avantage d'être "statique" (c'est-à-dire qu'il peut être défini dans un en-tête) sans ses défauts (il existe au plus une fois s'il n'est pas inséré)
Les variables locales statiques n'ont pas de lien (elles ne peuvent pas être désignées par leur nom en dehors de leur portée), mais ont une durée de stockage statique (c'est-à-dire qu'elles sont globales, mais leur construction et leur destruction obéissent à des règles spécifiques).
Mélanger en ligne et statique aura alors les conséquences que vous avez décrites (même si la fonction est en ligne, la variable statique à l'intérieur ne le sera pas, et vous terminerez avec autant de variables statiques que vous avez d'unités de compilation, y compris la définition de vos fonctions statiques ).
Depuis que j'ai écrit la question, je l'ai essayée avec Visual Studio 2008. J'ai essayé d'activer toutes les options qui font que VS agit conformément aux normes, mais il est possible que j'en ai manqué quelques-unes. Voici les résultats:
Lorsque la fonction est simplement "en ligne", il n'y a qu'une seule copie de la variable statique.
Lorsque la fonction est "statique en ligne", il y a autant de copies qu'il y a d'unités de traduction.
La vraie question est maintenant de savoir si les choses doivent être ainsi, ou s'il s'agit d'une idiosyncrasie du compilateur Microsoft C++.
Je suppose donc que vous avez quelque chose comme ça:
void doSomething()
{
static int value ;
}
Vous devez comprendre que la variable statique à l'intérieur de la fonction, simplement, une variable globale cachée à tous sauf à la portée de la fonction, ce qui signifie que seule la fonction à l'intérieur de laquelle elle est déclarée peut l'atteindre.
L'intégration de la fonction ne changera rien:
inline void doSomething()
{
static int value ;
}
Il n'y aura qu'une seule variable globale masquée. Le fait que le compilateur essaie d'incorporer le code ne changera pas le fait qu'il n'y a qu'une seule variable globale cachée.
Maintenant, si votre fonction est déclarée statique:
static void doSomething()
{
static int value ;
}
Ensuite, il est "privé" pour chaque unité de compilation, ce qui signifie que chaque fichier CPP, y compris l'en-tête où la fonction statique est déclarée, aura sa propre copie privée de la fonction, y compris sa propre copie privée de la variable globale cachée, donc autant de variables que il y a des unités de compilation comprenant l'en-tête.
Ajout de "inline" à une fonction "statique" avec une variable "statique" à l'intérieur:
inline static void doSomething()
{
static int value ;
}
a le même résultat que de ne pas ajouter ce mot clé "inline", en ce qui concerne la variable statique à l'intérieur.
Le comportement de VC++ est donc correct, et vous vous méprenez sur le vrai sens de "en ligne" et "statique".
Je crois que le compilateur crée de nombreuses copies de la variable, mais l'éditeur de liens en choisit une et fait que toutes les autres la référencent. J'ai eu des résultats similaires lorsque j'ai essayé une expérience pour créer différentes versions d'une fonction en ligne; si la fonction n'était pas réellement intégrée (mode débogage), tous les appels sont passés à la même fonction, quel que soit le fichier source à partir duquel ils ont été appelés.
Pensez comme un compilateur un instant - comment pourrait-il en être autrement? Chaque unité de compilation (fichier source) est indépendante des autres et peut être compilée séparément; chacun doit donc créer une copie de la variable, pensant qu'elle est la seule. L'éditeur de liens a la capacité d'atteindre au-delà de ces limites et d'ajuster les références pour les variables et les fonctions.
J'ai trouvé la réponse de Mark Ransom utile - que le compilateur crée de nombreuses copies de la variable statique, mais l'éditeur de liens en choisit une et l'applique à toutes les unités de traduction.
Ailleurs, j'ai trouvé ceci:
Voir [dcl.fct.spec]/4
[..] Une fonction en ligne avec liaison externe doit avoir la même adresse dans toutes les unités de traduction. Une variable locale statique dans une fonction en ligne externe fait toujours référence au même objet. Un littéral de chaîne dans une fonction en ligne externe est le même objet dans différentes unités de traduction.
Je n'ai pas de copie de la norme à vérifier, mais elle correspond à mon expérience dans l'examen de l'assemblage dans VS Express 2008
C'est censé être ainsi. "statique" indique au compilateur que vous voulez que la fonction soit locale à l'unité de compilation, donc vous voulez une copie par unité de compilation et une copie des variables statiques par instance de la fonction.
"inline" utilisé pour dire au compilateur que vous voulez que la fonction soit en ligne; de nos jours, cela prend juste comme "c'est ok s'il y a plusieurs copies du code, assurez-vous juste que c'est la même fonction". Donc tout le monde partage les variables statiques.
Remarque: cette réponse a été écrite en réponse à la réponse que l'affiche originale s'est affichée.
Depuis que j'ai écrit la question, je l'ai essayée avec Visual Studio 2008. J'ai essayé d'activer toutes les options qui font que VS agit conformément aux normes, mais il est possible que j'en ai manqué quelques-unes. Voici les résultats:
Lorsque la fonction est simplement "en ligne", il n'y a qu'une seule copie de la variable statique.
Lorsque la fonction est "statique en ligne", il y a autant de copies qu'il y a d'unités de traduction.
La vraie question est maintenant de savoir si les choses doivent être ainsi, ou s'il s'agit d'une idéosyncratie du compilateur Microsoft C++.