Classes de stockage

© D. Mathieu     mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 14/09/2001 - Dernière mise à jour : 14/09/2001

Sommaire

Objet automatique
Spécifieur auto
Spécifieur register
Objet statique
Déclaration/définition globale
Déclaration/définition globale avec initialisation
Déclarations globales multiples, lien externe
Déclarations globales multiples, lien interne
Déclaration locale, lien externe
Déclaration locale, lien interne
Objet statique et espace de noms
Espace de noms anonyme
Espace de noms nommé

Sommaire

   Le C/C++ offre deux classes de stockage : automatique et statique.

Sommaire

Objet automatique

    Un objet automatique est un objet qui est détruit dès que l'on sort du bloc {} dans lequel il est défini. La portée des identificateurs de ces objets (partie du code dans laquelle il est possible d'accéder aux objets par leur identificateur) est donc l'intervalle entre leur déclaration et la fin du bloc. La durée de vie de ces objets (période pendant laquelle les identificateurs sont associés à un espace en mémoire virtuelle) correspond exactement à leur portée. Leur visibilité correspond à leur portée s'ils ne sont pas masqués par la définition imbriquée d'un autre objet de même identificateur (pas du tout recommandé !) :
 
void f ()
{
    int i;     // début de la portée, de la durée de vie et de la visibilité de i
    // ...
    if (...)
    {
        int i; // nouvelle variable i qui masque la précédente définition
        // ...
    }          // fin de la portée, de la durée de vie et de la visibilité de la
               // variable interne i. La première variable i redevient visible
    // ...

}   // fin de la portée, de la durée de vie et de la visibilité 
    // de la première variable i. 

Exemple 1

    Une variable automatique n'est jamais initialisée par défaut (sauf bien sûr s'il s'agit d'un objet d'une classe, dont le constructeur est appelé).

Sommaire

Spécifieur auto

    Par défaut, les variables déclarées dans un bloc sont automatiques. On peut explicitement les faire précéder du spécifieur [1] de classe de stockage auto :
 
void f ()
{
    auto int i;     // début de la portée et de la visibilité de i
   // ...

}   // fin de la durée de vie et de la visibilité de la variable i. 

Exemple 2

    Les variables automatiques sont par défaut stockées dans la pile d'exécution du processus.

Sommaire

Spécifieur register

    Lorsque le spécifieur register est utilisé, le compilateur est sollicité pour que la variable correspondante soit placée dans un registre du processeur (accès beaucoup plus rapide). Cependant, ce n'est pas une obligation. Ce spécifieur n'a plus grande raison d'être utilisé, les compilateurs étant suffisamment optimisés pour effectuer de tels choix.

Sommaire

Objet statique

    Un objet statique est un objet dont la durée de vie s'étend entre sa définition et la fin du programme. Sa portée est très variable selon la façon dont il est déclaré.

Sommaire

Déclaration/définition globale

    Une déclaration est dite globale lorsqu'elle n'est pas interne à une classe ou à un objet, comme dans l'exemple ci-dessous :
 
// fichier main.cxx
// ...

int Global;

int main (...)
{
    cout << ++Global << endl;   // affiche 1

} // main() 

Exemple 3

    La variable Global est visible de la totalité du fichier main.cxx à partir de la ligne où elle est déclarée. Cette déclaration est aussi une définition : un espace mémoire lui est allouée dans un segment de mémoire virtuelle dite mémoire statique (sous Unix, ce segment est appelé segment bss : voir la Gestion de la mémoire sous Linux). Enfin, conformément à la norme C++, tous les octets sont mis à 0, la variable Global est donc initialisée à 0.

    Lors du lancement du programme, toutes les variables globales sont chargées avant que le contrôle ne soit donné à la fonction main().

Sommaire

Déclaration/définition globale avec initialisation

    Les variables globales peuvent être initialisées lors de leur déclaration. Elles sont aussi stockées en mémoire statique (sous Unix, ce segment est appelé segment data : voir la Gestion de la mémoire sous Linux). Ainsi, le programme suivant :
 
// fichier main.cxx
// ...

int Global = 12;

struct SObj
{
    int m_Membre;
    SObj (void) : m_Membre (::Global) 
    { 
        cout << "Constr. : " << m_Membre << endl;
    }
}; // SObj

SObj ObjGlobal;

int main ()

    cout << "Debut de main()" << endl;
    return 0;

} // main()

Exemple 4

provoque l'affichage :
 
Constr. : 12
Debut de main()

    Comme on le voit, l'objet ObjGlobal est construit avant l'exécution de la première instruction de la fonction main(), et le constructeur utilise la valeur de la variable Global déjà initialisée.

    Bien que non obligatoire, le préfixage des objets globaux par :: est fortement recommandé pour faciliter la compréhension.

Sommaire

Déclarations globales multiples, lien externe

    Comme nous venons de le montrer, les déclarations des variables globales Global et ObjGlobal sont aussi des définitions, avec réservation d'un espace mémoire. Lorsqu'un même objet doit être utilisé dans plusieurs fichiers .cxx qui seront donc compilés séparément (on les appelle des unités de compilation), chaque déclaration provoquerait une réservation mémoire distincte, il y aurait donc autant d'objets que de déclarations. C'est donc une erreur, détectée dans la phase d'édition de liens, comme le montre l'exemple suivant :
 
// fichier main.cxx
// ...

int Global;

extern void f1(); // déclaration de la fonction f1()

int main (...)
{
    cout << "++Global = " << ++Global << endl;
    f1();

} // main() 

// fichier f1.cxx
// ...

int Global;

void f1() 
{
    cout << "Global dans f1 = " << ++Global << endl;

} // f1() 

duo> g++ main.cxx f1.cxx
/tmp/ccMlT3IR.o(.bss+0x0): multiple definition of `Global'
/tmp/ccNTPdhr.o(.bss+0x4): first defined here

Exemple 5

    Il faut donc découpler la déclaration de la définition, ce qui ne peut être obtenu que par l'utilisation du mot réservé extern : toutes les déclarations d'un objet dans les différentes unités de compilation doivent donc être précédées du spécifieur de classe de stockage  extern, sauf l'une d'entre elles qui devient de ce fait une déclaration/définition. Il va de soi que, si la variable doit être explicitement initialisée (c'est-à-dire autrement que par 0), c'est au moment de sa définition (réservation de la mémoire) et non de sa déclaration qu'elle doit l'être. L'exemple ci-dessus devient :
 
// fichier main.cxx
// ...

extern int Global; // déclaration

extern void f1();

int main (...)
{
    cout << "++Global = " << ++Global << endl;
    f1();

} // main() 

// fichier f1.cxx
// ...

int Global = 12;  // déclaration/définition

void f1() { cout << "Global dans f1 = " << ++Global << endl; } 

Exemple 6

qui provoque l'affichage :
 
++Global = 13
Global dans f1 = 14

    On dit que la présence du spécifieur extern crée un lien externe à la variable Global.

    La portée, la visibilité et la durée de vie se confondent, sauf si la visibilité de l'identificateur est provisoirement masquée par la déclaration d'un autre objet homonyme.

Sommaire

Déclarations globales multiples, lien interne

    Il est possible de limiter la portée et la visibilité d'un objet global en utilisant le spécifieur static. Sa durée de vie correspond toujours à la durée d'exécution du programme, à partir de sa déclaration, mais sa portée est alors limitée à l'unité de compilation (fichier source .cxx et tous les fichiers inclus) après sa déclaration. Ainsi, plusieurs objets de même identificateur peuvent être déclarés/définis dans plusieurs unités de compilation, comme le montre l'exemple suivant :
 
// fichier main.cxx
// ...

extern int Global; // déclaration

extern void f1();
extern void f2();
extern void f3();

int main (...)
{
    cout << "Global dans main = " << ++Global << endl;
    f1();
    f2();
    f3();

} // main() 

// fichier f1.cxx
// ...

int Global = 12;  // déclaration/définition

void f1() { cout << "Global dans f1   = " << ++Global << endl; } 

// fichier f2.cxx
// ...

static int Global = 22;  // déclaration/définition

void f2() { cout << "Global dans f2   = " << ++Global << endl; } 

// fichier f3.cxx
// ...

static int Global = 32;  // déclaration/définition

void f3() { cout << "Global dans f3   = " << ++Global << endl; } 

Exemple 7

qui provoque l'affichage :
 
Global dans main = 13
Global dans f1   = 14
Global dans f2   = 23
Global dans f3   = 33

    Comme on le voit, il y a bien trois objets Global dans le programme, ceux des fichiers f2.cxx et f3.cxx masquant provsoirement celui de main.cxx et f1.cxx.

    On dit que la présence du spécifieur static crée un lien interne à la variable Global.

Sommaire

Déclaration locale, lien externe

    Dans une unité de compilation particulière, on peut déclarer une variable globale localement, c'est à dire dans un bloc {}. Cela n'affecte que sa portée qui, dans cette unité de compilation, est limitée au(x) bloc(s) dans le(s)quel(s) elle est déclarée. Ainsi :
 
// fichier main.cxx
// ...

int Global = 12;          // déclaration/définition

extern void f1();
extern void f2();

int main (...)
{
    cout << "Global dans main = " << ++Global << endl;
    f1();
    f2();

} // main() 

// fichier f12.cxx
// ...

void f1() 
{
    extern int Global;    // déclaration

    cout << "Global dans f1   = " << ++Global << endl; 

} // f1() 

void f2() { cout << "Global dans f2   = " << ++Global << endl; } 

Exemple 8

    Une erreur de syntaxe est signalée dans la fonction f2(), dans laquelle Global n'est pas déclarée.

    L'exemple suivant présente un cas plus surprenant :
 
// fichier main.cxx
// ...

int Global = 12;          // déclaration/définition

extern void f1();
extern void f2();

int main (...)
{
    cout << "Global dans main = " << ++Global << endl;
    f1();
    f2();

} // main() 

// fichier f12.cxx
// ...

void f1() 
{
    extern int Global;    // déclaration

    cout << "Global dans f1   = " << ++Global << endl; 

} // f1() 

static int Global = 32;   // déclaration/définition

void f2() 
{
    cout << "Global dans f2   = " << ++Global << endl; 

} // f2()

Exemple 9

dont l'exécution est la suivante :
 
Global dans main = 13
Global dans f1   = 33
Global dans f2   = 34

    Contrairement à ce que l'on pourrait attendre, la variable Global utilisée dans la fonction f2() est celle qui est locale au fichier f12.cxx, bien qu'elle soit définie plus loin. Le spécifieur extern qui précède la déclaration de Global dans la fonction f1() introduit un lien externe, mais cela ne signifie pas obligatoirement "externe à l'unité de compilation". Il faut plutôt l'interpréter comme "défini ailleurs ... ou plus loin" !

Sommaire

Déclaration locale, lien interne

    A l'inverse, on peut déclarer un identificateur localement à un bloc, (portée et visibilité limitées à ce bloc) tout en lui donnant une durée de vie d'une variable globale, soit toute la durée du programme. Considérons l'exemple suivant :
 
// fichier main.cxx
// ...

void f1 ()
{
    static int Global = 12;          // déclaration/définition

    cout << "Global dans f1   = " << ++Global << endl; 

} // f1()

int main (...)
{
    // cout << "Global dans main = " << ++Global << endl;

    for (int i = 3; --i; ) f1();

} // main() 

Exemple 10

    L'affichage dans la fonction main() est en commentaire car elle provoque une erreur de syntaxe : Global n'est pas déclarée.

    L'exécution de ce programme est la suivante :
 
Global dans f1   = 13
Global dans f1   = 14
Global dans f1   = 15

    Malgré les apparences, la variable Global n'est pas une variable automatique, stockée dans la pile au moment de l'appel de f1() et détruite à sa sortie. C'est au contraire une variable globale, déclarée/définie lors du premier appel de la fonction, et dont l'existence perdure au-delà de la sortie.

    Attention : l'initialisation ne peut être dissociée de la déclaration, car elle serait effectuée à chaque appel, comme dans le cas suivant :
 
// fichier main.cxx
// ...

void f1 ()
{
    static int Global;          // déclaration/définition
    Global = 12;                // réinitialisation à chaque appel

    cout << "Global dans f1   = " << ++Global << endl; 

} // f1()

// ...

Exemple 11

    ce qui ne présenterait plus aucun intérêt.
 

Sommaire

Objet statique et espace de noms

    Les indications ci-dessus concernent aussi bien le langage C que le C++. En revanche, avec la notion d'espace de noms, le C++ offre la possibilité de limiter la portée des variables, globales en particulier.

Sommaire

Espace de noms anonyme

    L'espace de noms anonyme est équivalent à l'utilisation d'une déclaration globale multiple avec un lien interne. L'exemple 7 peut être réécrit :
 
// fichier main.cxx
// ...

extern void f1();
extern void f2();

int main (...)
{
    // cout << "Global dans main = " << ++Global << endl;
    f1();
    f2();

} // main() 

// fichier f12.cxx
// ...
namespace
{
    int Global = 12;  // déclaration/définition

} // namespace anonyme

void f1() { cout << "Global dans f1   = " << ++Global << endl; } 
void f2() { cout << "Global dans f2   = " << ++Global << endl; } 

dont l'exécution provoque l'affichage suivant :
 
Global dans f1   = 13
Global dans f2   = 14

Exemple 12

    Comme précédemment, l'affichage dans la fonction main() est en commentaire car elle provoque une erreur de syntaxe : Global n'est pas déclarée.

    Tout se passe comme si Global était déclaré précédé de static.

Sommaire

Espace de noms nommé

    L'existence des espaces de noms nommés modifie la portée et la visibilité des identificateurs. La durée de vie des objets correspondants est cependant celle des objets globaux, à savoir toute la durée du programme :
 
// fichier main.cxx
// ...
namespace nsGlobal { int Global = 12; }

extern void f1();
extern void f2();

using namespace nsGlobal;

int main ()
{
    cout << "Global dans main = " << ++Global << endl;
    f1();
    f2();

    return 0;

} // main()
 

// fichier f12.cxx
// ...
namespace nsGlobal { extern int Global; }

void f1()
{   
    cout << "Global dans f1   = " << ++nsGlobal::Global << endl;

} // f1()

static int Global = 22;

void f2()
{   
    cout << "Global dans f2   = " << ++Global << endl;

} // f2()

dont l'exécution provoque l'affichage suivant :
 
Global dans main = 13
Global dans f1   = 14
Global dans f2   = 23

Exemple 13

Remarque : l'affichage de Global dans la fonction f2() serait mieux écrit :
 
cout << "Global dans f2   = " << ++::Global << endl;
//                                 --



[1] aussi appelé spécificateur dans certaines traductions.

© D. Mathieu     mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique