Recherche…


Introduction

Une classe de stockage est utilisée pour définir la portée d'une variable ou d'une fonction. En connaissant la classe de stockage d'une variable, nous pouvons déterminer la durée de vie de cette variable pendant l'exécution du programme.

Syntaxe

  • [auto | register | static | extern] <type de données> <nom de la variable> [= <valeur>];

  • [static _Thread_local | extern _Thread_local | _Thread_local] <Type de données> <Nom de la variable> [= <Valeur>]; / * depuis> = C11 * /

  • Exemples:

  • typedef int foo ;

  • extern int foo [2];

Remarques

Les spécificateurs de classe de stockage sont les mots-clés qui peuvent apparaître à côté du type de niveau supérieur d'une déclaration. L'utilisation de ces mots-clés affecte la durée de stockage et le couplage de l'objet déclaré, selon qu'il est déclaré à portée de fichier ou à portée de bloc:

Mot-clé Durée de stockage Lien Remarques
static Statique Interne Définit la liaison interne pour les objets à portée de fichier; définit la durée de stockage statique pour les objets à portée de bloc.
extern Statique Externe Implicite et donc redondant pour les objets définis au niveau du fichier qui ont également un initialiseur. Lorsqu'elle est utilisée dans une déclaration à portée de fichier sans initialiseur, cela indique que la définition doit être trouvée dans une autre unité de traduction et sera résolue au moment de la liaison.
auto Automatique Sans importance Implicite et donc redondant pour les objets déclarés à portée de bloc.
register Automatique Sans importance Ne concerne que les objets avec une durée de stockage automatique. Indique que la variable doit être stockée dans un registre. Une contrainte imposée est que l'on ne peut pas utiliser l'opérateur unaire & "address of" sur un tel objet, et par conséquent l'objet ne peut pas être aliasé.
typedef Sans importance Sans importance Pas un spécificateur de classe de stockage dans la pratique, mais fonctionne comme un point de vue syntaxique. La seule différence est que l'identifiant déclaré est un type plutôt qu'un objet.
_Thread_local Fil Interne externe Introduit en C11, pour représenter la durée de stockage des threads . S'il est utilisé à portée de bloc, il doit également inclure extern ou static .

Chaque objet a une durée de stockage associée (indépendamment de la portée) et une liaison (pertinente pour les déclarations à portée de fichier uniquement), même lorsque ces mots-clés sont omis.

L'ordre des spécificateurs de classe de stockage en fonction des spécificateurs de type de niveau supérieur ( int , unsigned , short , etc.) et des qualificateurs de niveau supérieur ( const , volatile ) n'est pas appliqué. Ces deux déclarations sont donc valides:

int static const unsigned a = 5; /* bad practice */
static const unsigned int b = 5; /* good practice */

Toutefois, il est conseillé de placer les spécificateurs de classe de stockage en premier, puis tous les qualificatifs de type, puis le spécificateur de type ( void , char , int , signed long , unsigned long long , long double ...).

Tous les spécificateurs de classe de stockage ne sont pas légaux à une certaine portée:

register int x; /* legal at block scope, illegal at file scope */
auto int y; /* same */

static int z; /* legal at both file and block scope */
extern int a; /* same */

extern int b = 5; /* legal and redundant at file scope, illegal at block scope */

/* legal because typedef is treated like a storage class specifier syntactically */
int typedef new_type_name;

Durée de stockage

La durée de stockage peut être statique ou automatique. Pour un objet déclaré, il est déterminé en fonction de son étendue et des spécificateurs de classe de stockage.

Durée de stockage statique

Les variables avec une durée de stockage statique sont présentes tout au long de l'exécution du programme et peuvent être déclarées à la fois à la portée du fichier (avec ou sans static ) et à la portée du bloc (en mettant explicitement la static ). Ils sont généralement alloués et initialisés par le système d'exploitation au démarrage du programme et récupérés à la fin du processus. En pratique, les formats exécutables ont des sections dédiées pour ces variables ( data , bss et rodata ) et ces sections entières du fichier sont mappées en mémoire à certaines plages.

Durée de stockage des threads

C11

Cette durée de stockage a été introduite en C11. Ce n'était pas disponible dans les normes C précédentes. Certains compilateurs fournissent une extension non standard avec une sémantique similaire. Par exemple, gcc prend en __thread spécificateur __thread qui peut être utilisé dans les normes C précédentes qui n'avaient pas _Thread_local .

Les variables avec une durée de stockage des threads peuvent être déclarées à la fois dans la portée du fichier et dans la portée du bloc. S'il est déclaré au niveau du bloc, il doit également utiliser un spécificateur de stockage static ou extern . Sa durée de vie est l'exécution complète du thread dans lequel il a été créé. C'est le seul spécificateur de stockage pouvant apparaître à côté d'un autre spécificateur de stockage.

Durée de stockage automatique

Les variables avec une durée de stockage automatique ne peuvent être déclarées qu'au niveau du bloc (directement dans une fonction ou dans un bloc de cette fonction). Ils ne sont utilisables que pendant la période entre l'entrée et la sortie de la fonction ou du bloc. Une fois la variable hors de portée (soit en revenant de la fonction, soit en quittant le bloc), son stockage est automatiquement désalloué. Toute autre référence à la même variable à partir de pointeurs est invalide et conduit à un comportement indéfini.

Dans les implémentations classiques, les variables automatiques sont situées à certains décalages dans le cadre de la pile d'une fonction ou dans des registres.

Liaison externe et interne

La liaison ne concerne que les objets (fonctions et variables) déclarés au niveau du fichier et affecte leur visibilité sur différentes unités de traduction. Les objets avec liaison externe sont visibles dans toutes les autres unités de traduction (à condition que la déclaration appropriée soit incluse). Les objets avec liaison interne ne sont pas exposés à d'autres unités de traduction et ne peuvent être utilisés que dans l'unité de traduction où ils sont définis.

typedef

Définit un nouveau type basé sur un type existant. Sa syntaxe reflète celle d'une déclaration de variable.

/* Byte can be used wherever `unsigned char` is needed */
typedef unsigned char Byte;

/* Integer is the type used to declare an array consisting of a single int */
typedef int Integer[1];

/* NodeRef is a type used for pointers to a structure type with the tag "node" */
typedef struct node *NodeRef;

/* SigHandler is the function pointer type that gets passed to the signal function. */
typedef void (*SigHandler)(int);

Bien que ce ne soit pas techniquement une classe de stockage, un compilateur le traitera comme une classe, car aucune des autres classes de stockage n'est autorisée si le mot-clé typedef est utilisé.

Les typedef s sont importants et ne doivent pas être remplacés par la macro #define .

typedef int newType; 
newType *ptr;        // ptr is pointer to variable of type 'newType' aka int

cependant,

#define int newType
newType *ptr;        // Even though macros are exact replacements to words, this doesn't result to a pointer to variable of type 'newType' aka int

auto

Cette classe de stockage indique qu'un identifiant a une durée de stockage automatique. Cela signifie qu'une fois que l'étendue dans laquelle l'identifiant a été défini se termine, l'objet désigné par l'identifiant n'est plus valide.

Puisque tous les objets, ne vivant pas dans la portée globale ou étant déclarés static , ont une durée de stockage automatique par défaut lorsqu'ils sont définis, ce mot clé présente un intérêt historique et ne doit pas être utilisé:

int foo(void)
{
    /* An integer with automatic storage duration. */
    auto int i = 3;

    /* Same */
    int j = 5;

    return 0;
} /* The values of i and j are no longer able to be used. */

statique

La classe de stockage static remplit différentes fonctions, en fonction de l'emplacement de la déclaration dans le fichier:

  1. Pour confiner l'identifiant à cette unité de traduction uniquement (scope = fichier).

    /* No other translation unit can use this variable. */
    static int i;
    
    /* Same; static is attached to the function type of f, not the return type int. */
    static int f(int n);
    
  2. Pour enregistrer des données à utiliser avec le prochain appel d'une fonction (scope = block):

     void foo()
     {
         static int a = 0; /* has static storage duration and its lifetime is the
                            * entire execution of the program; initialized to 0 on 
                            * first function call */ 
         int b = 0; /* b has block scope and has automatic storage duration and 
                     * only "exists" within function */
         
         a += 10;
         b += 10; 
    
         printf("static int a = %d, int b = %d\n", a, b);
     }
    
     int main(void)
     {
         int i;
         for (i = 0; i < 5; i++)
         {
             foo();
         }
    
         return 0;
     }
    

    Ce code imprime:

     static int a = 10, int b = 10
     static int a = 20, int b = 10
     static int a = 30, int b = 10
     static int a = 40, int b = 10
     static int a = 50, int b = 10
    

Les variables statiques conservent leur valeur même lorsqu'elles sont appelées depuis plusieurs threads différents.

C99
  1. Utilisé dans les paramètres de fonction pour indiquer qu'un tableau doit avoir un nombre minimal constant d'éléments et un paramètre non nul:

    /* a is expected to have at least 512 elements. */
    void printInts(int a[static 512])
    {
        size_t i;
        for (i = 0; i < 512; ++i)
            printf("%d\n", a[i]);
    }
    

    Le nombre requis d'éléments (ou même un pointeur non nul) n'est pas nécessairement vérifié par le compilateur et les compilateurs ne sont pas tenus de vous informer de quelque manière que ce soit si vous ne disposez pas d'assez d'éléments. Si un programmeur transmet moins de 512 éléments ou un pointeur nul, le comportement non défini est le résultat. Comme il est impossible d'appliquer cette règle, il faut faire particulièrement attention lors de la transmission d'une valeur pour ce paramètre à une telle fonction.

externe

Utilisé pour déclarer un objet ou une fonction défini ailleurs (et qui a un lien externe ). En général, il est utilisé pour déclarer un objet ou une fonction à utiliser dans un module autre que celui dans lequel l'objet ou la fonction correspondant est défini:

/* file1.c */
int foo = 2;  /* Has external linkage since it is declared at file scope. */
/* file2.c */
#include <stdio.h>
int main(void)
{
    /* `extern` keyword refers to external definition of `foo`. */
    extern int foo;
    printf("%d\n", foo);
    return 0;
}
C99

Les choses deviennent un peu plus intéressantes avec l'introduction du mot clé en inline dans C99:

/* Should usually be place in a header file such that all users see the definition */
/* Hints to the compiler that the function `bar` might be inlined */
/* and suppresses the generation of an external symbol, unless stated otherwise. */
inline void bar(int drink)
{
    printf("You ordered drink no.%d\n", drink);
}

/* To be found in just one .c file.
   Creates an external function definition of `bar` for use by other files.
   The compiler is allowed to choose between the inline version and the external
   definition when `bar` is called. Without this line, `bar` would only be an inline
   function, and other files would not be able to call it. */
extern void bar(int);

registre

Des astuces pour le compilateur selon lesquelles l'accès à un objet doit être aussi rapide que possible. Si le compilateur utilise réellement le conseil est défini par l'implémentation; il peut simplement le traiter comme équivalent à auto .

La seule propriété qui est définitivement différente pour tous les objets déclarés avec register est que leur adresse ne peut pas être calculée. Ce register peut être un bon outil pour assurer certaines optimisations:

register size_t size = 467;

est un objet qui ne peut jamais créer d'alias car aucun code ne peut transmettre son adresse à une autre fonction où il pourrait être modifié de manière inattendue.

Cette propriété implique également qu'un tableau

register int array[5];

ne peut pas se désintégrer en un pointeur vers son premier élément (c'est-à-dire que le array transforme en &array[0] ). Cela signifie que les éléments d'un tel tableau ne sont pas accessibles et que le tableau lui-même ne peut pas être transmis à une fonction.

En fait, la seule utilisation légale d'un tableau déclaré avec une classe de stockage de register est la sizeof opérateur; tout autre opérateur aurait besoin de l'adresse du premier élément du tableau. Pour cette raison, les tableaux ne devraient généralement pas être déclarés avec le mot-clé register car cela les rend inutiles pour autre chose que le calcul de la taille du tableau entier, ce qui peut se faire aussi facilement sans le mot-clé register .

La classe de stockage des register est plus appropriée pour les variables définies dans un bloc et accessibles avec une fréquence élevée. Par exemple,

/* prints the sum of the first 5 integers*/
/* code assumed to be part of a function body*/ 
{ 
    register int k, sum;
    for(k = 1, sum = 0; k < 6; sum += k, k++);
        printf("\t%d\n",sum);
}
C11

L'opérateur _Alignof peut également être utilisé avec register tableaux de register .

_Thread_local

C11

C'était un nouveau spécificateur de stockage introduit en C11 avec le multi-threading. Ce n'est pas disponible dans les normes C précédentes.

Indique la durée de stockage des threads . Une variable déclarée avec le _Thread_local stockage _Thread_local indique que l'objet est local pour ce thread et que sa durée de vie correspond à l'exécution complète du thread dans lequel il est créé. Il peut également apparaître avec static ou extern .

#include <threads.h>
#include <stdio.h>
#define SIZE 5

int thread_func(void *id)
{
    /* thread local variable i. */
    static _Thread_local int i;

    /* Prints the ID passed from main() and the address of the i.
     * Running this program will print different addresses for i, showing
     * that they are all distinct objects. */
    printf("From thread:[%d], Address of i (thread local): %p\n", *(int*)id, (void*)&i);

    return 0;
}

int main(void)
{
    thrd_t id[SIZE];
    int arr[SIZE] = {1, 2, 3, 4, 5};

    /* create 5 threads. */
    for(int i = 0; i < SIZE; i++) {
        thrd_create(&id[i], thread_func, &arr[i]);
    }

    /* wait for threads to complete. */
    for(int i = 0; i < SIZE; i++) {
        thrd_join(id[i], NULL);
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow