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

StackOverflow https://stackoverflow.com/questions/92546

  •  01-07-2019
  •  | 
  •  

Question

En refacturant certains #defines , je suis tombé sur des déclarations similaires à celles-ci 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, le statique fera-t-il? Notez que l’inclusion multiple des en-têtes n’est pas possible en raison du tour classique #ifndef HEADER #define HEADER #endif (si cela compte) .

Est-ce que statique signifie qu'une seule copie de VAL est créée, si l'en-tête est inclus par plusieurs fichiers sources?

Était-ce utile?

La solution

Le static signifie qu’une copie de VAL sera créée pour chaque fichier source dans lequel il est inclus. Mais cela signifie également que plusieurs inclusions ne se traduiront pas par plusieurs. définitions de VAL qui entreront en collision au moment du lien. En C, sans static , vous devez vous assurer qu'un seul fichier source est défini VAL , tandis que les autres fichiers source le déclarent extern . En général, on le fait en le définissant (éventuellement avec un initialiseur) dans un fichier source et en plaçant la déclaration extern dans un fichier d’en-tête.

Les variables

static au niveau global ne sont visibles dans leur propre fichier source, qu’elles y soient arrivées via un include ou dans le fichier principal.

Note de l'éditeur: En C ++, les objets const ne contenant ni les mots clés static ni extern ne sont dans leur déclaration implicitement statique .

Autres conseils

Les balises static et extern des variables à portée de fichier déterminent si elles sont accessibles dans d'autres unités de traduction (c'est-à-dire d'autres .c ou < code> .cpp ).

  • static donne la liaison interne variable, en la cachant des autres unités de traduction. Toutefois, les variables avec une liaison interne peuvent être définies dans plusieurs unités de traduction.

  • extern donne le lien externe variable, le rendant visible par d'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 statique ou extern ) est l'une des zones dans lesquelles C et C ++ diffèrent.

  • En C, les variables de fichier sont extern (liaison externe) par défaut. Si vous utilisez C, VAL est statique et ANOTHER_VAL est extern .

  • En C ++, les variables de fichier couvertes sont statique (liaison interne) par défaut si elles sont const et extern par défaut s'ils ne le sont pas. Si vous utilisez C ++, VAL et ANOTHER_VAL sont statique .

Extrait d'un brouillon de la spécification C :

  

6.2.2 Liaisons d'identifiants   ...   -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,   son lien est externe.

D'après 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 une liaison externe sauf s'il possède une liaison 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 une liaison interne.

La statique signifie que vous obtenez une copie par fichier, mais contrairement à d’autres, il est parfaitement légal de le faire. Vous pouvez facilement le tester 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;
}

En cours d'exécution, cela vous donne cette sortie:

  

0x446020
  0x446040

Les variables

const en C ++ ont un lien interne. Donc, utiliser static n'a aucun effet.

a.h

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

two.cpp

#include "a.h"

func1()
{
   cout << i;
}

S'il s'agissait d'un programme C, vous obtiendrez l'erreur 'Plusieurs définitions' pour i (en raison d'un lien externe).

La déclaration statique à ce niveau de code signifie que la variable est uniquement visible dans l'unité de compilation en cours. 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 statique et que cet en-tête est inclus dans plusieurs fichiers C / CPP, cette variable sera alors "local". à ces modules. Il y aura N copies de cette variable pour les N places où cet en-tête est inclus. Ils ne sont pas liés les uns aux autres. Tout code contenu 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 présenter d'avantage. Il se peut que je manque quelque chose, mais cela ne semble pas avoir d’importance - je n’ai jamais rien vu de tel.

En ce qui concerne l'inline, dans ce cas, la variable est probablement en ligne, mais c'est uniquement parce qu'elle est déclarée const. Le compilateur pourrait être plus susceptible d’insérer des variables statiques de module, mais cela dépend de la situation et du code en cours de compilation. Il n’existe aucune garantie que le compilateur intègre la "statique".

Le livre C (gratuit en ligne) contient un chapitre sur les liens, qui explique plus en détail la signification du terme "statique" (bien que la réponse correcte soit déjà donnée dans d'autres commentaires): http://publications.gbdirect.co.uk/c_book/chapter4/linkage. html

Pour répondre à la question, "le terme statique signifie-t-il qu'une seule copie de VAL est créée, si l'en-tête est inclus dans plusieurs fichiers sources?" ...

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

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

  

En C, les variables de fichier sont définies par défaut en extern. Si vous utilisez C, VAL est statique et ANOTHER_VAL est extern.

Notez que les lieurs modernes peuvent se plaindre d'ANOTHER_VAL si l'en-tête est inclus dans différents fichiers (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 dont la portée est définie sur un fichier sont statiques par défaut si elles sont const et externes par défaut si elles ne le sont pas. Si vous utilisez C ++, VAL et ANOTHER_VAL sont statiques.

Vous devez également prendre en compte le fait que les deux variables sont désignées par const. Dans l’idéal, le compilateur choisirait toujours d’inscrire ces variables en ligne et de ne pas inclure de stockage pour celles-ci. Il existe une foule de raisons pour lesquelles le stockage peut être alloué. Celles auxquelles je peux penser ...

  • options de débogage
  • adresse prise dans le fichier
  • le compilateur alloue toujours le stockage (les types const complexes ne peuvent pas être facilement alignés, ce qui en fait un cas particulier pour les types de base)

Vous ne pouvez pas déclarer une variable statique sans la définir également (car 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 incluant le fichier d’en-tête possède sa propre copie privée de la variable, ce qui n’est probablement pas ce à quoi il était destiné.

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 "liaison interne". Dans ce cas, puisqu'il est déclaré const , il peut être optimisé / intégré par le compilateur. Si vous omettez const , le compilateur doit allouer de la mémoire dans chaque unité de compilation.

En omettant statique , le lien est extern par défaut. Encore une fois, const a été enregistré: le compilateur peut optimiser / utiliser en ligne. Si vous supprimez const , vous obtiendrez une erreur lorsqu’il aura multiplié les symboles définis au moment de la liaison.

Les variables

const sont statiques par défaut en C ++, mais en externe C. Par conséquent, si vous utilisez C ++, vous ne pouvez pas savoir quelle construction utiliser.

(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

Static empêche une autre unité de compilation d’externaliser cette variable, de sorte que le compilateur peut simplement "insérer" et insérer "en ligne". la valeur de la variable où elle est utilisée et ne pas créer de 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.

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top