Déclarations de variables dans les fichiers d'en-tête - statiques ou non?

91

Lors de la refactorisation de certaines, #definesje suis tombé sur des déclarations similaires aux suivantes dans un fichier d'en-tête C ++:

static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;

La question est de savoir quelle différence, le cas échéant, la statique fera-t-elle? Notez que l'inclusion multiple des en-têtes n'est pas possible en raison de l' #ifndef HEADER #define HEADER #endifastuce classique (si cela compte).

La valeur statique signifie-t-elle qu'une seule copie de VALest créée, au cas où l'en-tête serait inclus par plus d'un fichier source?

Rob
la source
en relation: stackoverflow.com/questions/177437/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

107

Les staticmoyens qu'il y aura une copie VALpour chaque fichier source , il est inclus. Mais elle a aussi des moyens que les inclusions multiples ne seront pas donner lieu à de multiples définitions de VALqui entrent en collision au moment de la liaison. En C, sans le, staticvous devez vous assurer qu'un seul fichier source est défini VALtandis que les autres fichiers source le déclarent extern. Habituellement, on le ferait en le définissant (éventuellement avec un initialiseur) dans un fichier source et en plaçant la externdéclaration dans un fichier d'en-tête.

static les variables au niveau global ne sont visibles que dans leur propre fichier source, qu'elles y soient parvenues via une inclusion ou qu'elles aient été dans le fichier principal.


Note de l'éditeur: en C ++, les constobjets sans mots-clés staticni externdans leur déclaration le sont implicitement static.

Justsalt
la source
Je suis fan de la dernière phrase, incroyablement utile. Je n'ai pas voté la réponse parce que 42 c'est mieux. edit: grammar
RealDeal_EE'18
"Le statique signifie qu'il y aura une copie de VAL créée pour chaque fichier source dans lequel il est inclus." Cela semble impliquer qu'il y aurait deux copies de VAL si deux fichiers source incluaient le fichier d'en-tête. J'espère que ce n'est pas vrai et qu'il y a toujours une seule instance de VAL, quel que soit le nombre de fichiers qui incluent l'en-tête.
Brent212
4
@ Brent212 Le compilateur ne sait pas si une déclaration / définition provient d'un fichier d'en-tête ou d'un fichier principal. Alors vous espérez en vain. Il y aura deux copies de VAL si quelqu'un a été stupide et a mis une définition statique dans un fichier d'en-tête et il a été inclus dans deux sources.
Justsalt
1
Les valeurs const ont un lien interne en C ++
adrianN
112

Les balises staticet des externvariables de portée fichier déterminent si elles sont accessibles dans d'autres unités de traduction (c.-à-d. Autres .cou .cppfichiers).

  • staticdonne le lien interne variable, en le cachant des autres unités de traduction. Cependant, les variables avec un lien interne peuvent être définies dans plusieurs unités de traduction.

  • externdonne le lien externe variable, le rendant visible aux autres unités de traduction. Cela signifie généralement que la variable ne doit être définie que dans une seule unité de traduction.

La valeur par défaut (lorsque vous ne spécifiez pas staticou extern) est l'un de ces domaines dans lesquels C et C ++ diffèrent.

  • En C, les variables de portée fichier sont extern(liaison externe) par défaut. Si vous utilisez C, VALest staticet ANOTHER_VALest extern.

  • En C ++, les variables de portée fichier sont static(liaison interne) par défaut si elles le sont const, et externpar défaut si elles ne le sont pas. Si vous utilisez C ++, les deux VALet ANOTHER_VALsont static.

À partir d'un brouillon de la spécification C :

6.2.2 Liens d'identificateurs ... -5- Si la déclaration d'un identificateur 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.

À partir d'un brouillon de la spécification C ++ :

7.1.1 - Spécificateurs de classe de stockage [dcl.stc] ... -6- Un nom déclaré dans une portée d'espace de noms sans spécificateur de classe de stockage a un lien externe à moins qu'il n'ait un lien interne en raison d'une déclaration précédente et à condition qu'il ne soit pas déclaré const. Les objets déclarés const et non explicitement déclarés extern ont un lien interne.

bk1e
la source
47

Le statique signifiera que vous obtenez une copie par fichier, mais contrairement à d'autres, il est parfaitement légal de le faire. Vous pouvez facilement tester cela avec un petit exemple de code:

test.h:

static int TEST = 0;
void test();

test1.cpp:

#include <iostream>
#include "test.h"

int main(void) {
    std::cout << &TEST << std::endl;
    test();
}

test2.cpp:

#include <iostream>
#include "test.h"

void test() {
    std::cout << &TEST << std::endl;
}

L'exécution de ceci vous donne cette sortie:

0x446020
0x446040

tranches de citron vert
la source
5
Merci pour l'exemple!
Kyrol
Je me demande si TESTc'était le cas const, si LTO serait capable de l'optimiser dans un seul emplacement de mémoire. Mais -O3 -fltode GCC 8.1 ne l'a pas fait.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Ce serait illégal de le faire - même si c'est constant, statique garantit que chaque instance est locale à l'unité de compilation. Il pourrait probablement incorporer la valeur de la constante elle-même s'il était utilisé comme constante, mais puisque nous prenons son adresse, il doit renvoyer un pointeur unique.
slicedlime
6

constles variables en C ++ ont un lien interne. Donc, l'utilisation staticn'a aucun effet.

ah

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

deux.cpp

#include "a.h"

func1()
{
   cout << i;
}

S'il s'agissait d'un programme C, vous obtiendrez une erreur de «définition multiple» pour i(en raison d'un lien externe).

Nitine
la source
2
Eh bien, l'utilisation statica pour effet de signaler clairement l'intention et la conscience de ce que l'on est en train de coder, ce qui n'est jamais une mauvaise chose. Pour moi, c'est comme inclure virtuallors de la neutralisation: nous n'avons pas à le faire, mais les choses semblent beaucoup plus intuitives - et cohérentes avec d'autres déclarations - lorsque nous le faisons.
underscore_d
Vous pouvez obtenir une erreur de définition multiple dans C. Il s'agit d'un comportement non défini sans diagnostic requis
MM
5

La déclaration statique à ce niveau de code signifie que le variabel n'est visible que dans l'unité de compilation courante. Cela signifie que seul le code de ce module verra cette variable.

si vous avez un fichier d'en-tête qui déclare une variable static et que cet en-tête est inclus dans plusieurs fichiers C / CPP, alors cette variable sera "locale" pour ces modules. Il y aura N copies de cette variable pour les N endroits où l'en-tête est inclus. Ils ne sont pas du tout liés les uns aux autres. Tout code dans l'un de ces fichiers source ne fera référence qu'à la variable déclarée dans ce module.

Dans ce cas particulier, le mot-clé "statique" ne semble pas apporter de bénéfice. Il me manque peut-être quelque chose, mais cela ne semble pas avoir d'importance - je n'ai jamais rien vu de tel auparavant.

Quant à l'inlining, dans ce cas, la variable est probablement inline, mais c'est uniquement parce qu'elle est déclarée const. Le compilateur est peut- être plus susceptible d'insérer des variables statiques de module en ligne, mais cela dépend de la situation et du code en cours de compilation. Il n'y a aucune garantie que le compilateur intégrera la «statique».

marque
la source
L'avantage de «statique» ici est qu'autrement vous déclarez plusieurs globaux tous avec le même nom, un pour chaque module qui inclut l'en-tête. Si l'éditeur de liens ne se plaint pas, c'est uniquement parce qu'il mord sa langue et qu'il est poli.
Dans ce cas, en raison du const, le staticest implicite et donc facultatif. Le corollaire est qu'il n'y a pas de risque d'erreurs de définition multiples comme l'a affirmé Mike F.
underscore_d
2

Pour répondre à la question, "le statique signifie-t-il qu'une seule copie de VAL est créée, dans le cas où l'en-tête est inclus par plus d'un fichier source?" ...

NON . VAL sera toujours défini séparément dans chaque fichier qui comprend l'en-tête.

Les normes pour C et C ++ provoquent une différence dans ce cas.

En C, les variables de portée fichier sont externes par défaut. Si vous utilisez C, VAL est statique et ANOTHER_VAL est externe.

Notez que les éditeurs de liens modernes peuvent se plaindre de ANOTHER_VAL si l'en-tête est inclus dans des fichiers différents (même nom global défini deux fois), et se plaindraient certainement si ANOTHER_VAL était initialisé à une valeur différente dans un autre fichier

En C ++, les variables de portée fichier sont statiques par défaut si elles sont const et extern par défaut si elles ne le sont pas. Si vous utilisez C ++, VAL et ANOTHER_VAL sont statiques.

Vous devez également tenir compte du fait que les deux variables sont désignées const. Idéalement, le compilateur choisirait toujours d'inline ces variables et de ne pas inclure de stockage pour elles. Il existe une multitude de raisons pour lesquelles le stockage peut être alloué. Ceux auxquels je peux penser ...

  • options de débogage
  • adresse prise dans le dossier
  • le compilateur alloue toujours le stockage (les types const complexes ne peuvent pas être facilement insérés, cela devient donc un cas particulier pour les types de base)
itj
la source
Remarque: Dans la machine abstraite, il y a une copie de VAL dans chaque unité de traduction distincte qui comprend l'en-tête. En pratique, l'éditeur de liens peut décider de les combiner de toute façon, et le compilateur peut en optimiser tout ou partie en premier.
MM
1

En supposant que ces déclarations ont une portée globale (c'est-à-dire qu'elles ne sont pas des variables membres), alors:

statique signifie «lien interne». Dans ce cas, puisqu'il est déclaré const, cela peut être optimisé / intégré par le compilateur. Si vous omettez le const, le compilateur doit allouer du stockage dans chaque unité de compilation.

En omettant statique, le lien est externe par défaut. Encore une fois, vous avez été enregistré par la const ness - le compilateur peut optimiser / utilisation en ligne. Si vous supprimez le const, vous obtiendrez une erreur de symboles multiples définis au moment de la liaison.

Seb Rose
la source
Je crois que le compilateur doit allouer de l'espace pour un const int dans tous les cas, car un autre module pourrait toujours dire "extern const int n'importe quoi; quelque chose (& peu importe);"
1

Vous ne pouvez pas déclarer une variable statique sans la définir également (c'est parce que les modificateurs de classe de stockage static et extern s'excluent mutuellement). Une variable statique peut être définie dans un fichier d'en-tête, mais cela ferait en sorte que chaque fichier source contenant le fichier d'en-tête ait sa propre copie privée de la variable, ce qui n'est probablement pas ce qui était prévu.

Gajendra Kumar
la source
"... mais cela ferait en sorte que chaque fichier source contenant le fichier d'en-tête ait sa propre copie privée de la variable, ce qui n'est probablement pas ce qui était prévu." - En raison du fiasco de l'ordre d'initialisation statique , il peut être nécessaire d'en avoir une copie dans chaque unité de traduction.
jww
1

Les variables const sont par défaut statiques en C ++, mais extern C. Donc, si vous utilisez C ++, la construction à utiliser n'a aucun sens.

(7.11.6 C ++ 2003 et Apexndix C a des exemples)

Exemple de comparaison des sources de compilation / liaison en tant que programme C et C ++:

bruziuz:~/test$ cat a.c
const int b = 22;
int main(){return 0;}
bruziuz:~/test$ cat b.c
const int b=2;
bruziuz:~/test$ gcc -x c -std=c89 a.c b.c
/tmp/ccSKKIRZ.o:(.rodata+0x0): multiple definition of `b'
/tmp/ccDSd0V3.o:(.rodata+0x0): first defined here
collect2: error: ld returned 1 exit status
bruziuz:~/test$ gcc -x c++ -std=c++03 a.c b.c 
bruziuz:~/test$ 
bruziuz:~/test$ gcc --version | head -n1
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609
Bruziuz
la source
Il est judicieux d'inclure toujours le static. Il signale l'intention / la conscience de ce que fait le programmeur et maintient la parité avec d'autres types de déclaration (et, fwiw, C) qui n'ont pas l'implicite static. C'est comme inclure virtualet dernièrement overridedans les déclarations de fonctions primordiales - pas nécessaire, mais beaucoup plus auto-documenté et, dans le cas de ces dernières, propice à l'analyse statique.
underscore_d
Je suis absolument d'accord. Par exemple, moi dans la vraie vie, je l'écris toujours explicitement.
bruziuz
"Donc, si vous utilisez C ++, la construction à utiliser n'a aucun sens ..." - Hmm ... Je viens de compiler un projet qui constn'est utilisé que sur une variable dans un en-tête avec g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2). Il en a résulté environ 150 symboles multi-définis (un pour chaque unité de traduction, l'en-tête était inclus). Je pense que nous avons besoin soit static, inlinesoit d'un espace de noms anonyme / sans nom pour éviter le lien externe.
jww
J'ai essayé baby-example avec gcc-5.4 avec declare const intdans la portée de l'espace de noms et dans l'espace de noms global. Et il est compilé et suit la règle "Les objets déclarés const et non explicitement déclarés extern ont un lien interne." ".... Peut-être dans le projet, pour une raison quelconque, cet en-tête inclus dans les sources compilées en C, où les règles complètement différentes.
bruziuz
@jww J'ai téléchargé un exemple avec un problème de liaison pour C et aucun problème pour C ++
bruziuz
0

Static empêche une autre unité de compilation d'externaliser cette variable afin que le compilateur puisse simplement "insérer" la valeur de la variable là où elle est utilisée et ne pas créer de stockage mémoire pour elle.

Dans votre deuxième exemple, le compilateur ne peut pas supposer qu'un autre fichier source ne l'externera pas, il doit donc stocker cette valeur quelque part en mémoire.

Jim Buck
la source
-2

Static empêche le compilateur d'ajouter plusieurs instances. Cela devient moins important avec la protection #ifndef, mais en supposant que l'en-tête est inclus dans deux bibliothèques séparées et que l'application est liée, deux instances seraient incluses.

Superpolock
la source
en supposant que par "bibliothèques" vous entendez des unités de traduction , alors non, les inclus-gardes ne font absolument rien pour empêcher les définitions multiples, étant donné qu'elles se protègent uniquement contre les inclusions répétées dans la même unité de traduction . ainsi, ils ne font rien du tout pour rendre static«moins important». et même avec les deux, vous pouvez vous retrouver avec plusieurs définitions liées en interne, ce qui n'est probablement pas prévu.
underscore_d