C Language
Classes de stockage
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
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:
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);
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.
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;
}
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);
}
L'opérateur _Alignof
peut également être utilisé avec register
tableaux de register
.
_Thread_local
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);
}
}