Ricerca…


introduzione

Una classe di archiviazione viene utilizzata per impostare l'ambito di una variabile o funzione. Conoscendo la classe di archiviazione di una variabile, possiamo determinare il tempo di vita di tale variabile durante il tempo di esecuzione del programma.

Sintassi

  • [auto | register | static | extern] <Tipo di dati> <Nome variabile> [= <Valore>];

  • [static _Thread_local | extern _Thread_local | _Thread_local] <Tipo di dati> <Nome variabile> [= <Valore>]; / * poiché> = C11 * /

  • Esempi:

  • typedef int foo ;

  • extern int foo [2];

Osservazioni

Gli identificatori di classe di archiviazione sono le parole chiave che possono essere visualizzate accanto al tipo di livello superiore di una dichiarazione. L'utilizzo di queste parole chiave influisce sulla durata e sul collegamento all'archiviazione dell'oggetto dichiarato, a seconda che sia dichiarato nell'ambito di un file o nell'ambito di un blocco:

Parola chiave Durata di archiviazione collegamento Osservazioni
static Statico Interno Imposta il collegamento interno per gli oggetti nell'ambito del file; imposta la durata di archiviazione statica per gli oggetti nell'ambito del blocco.
extern Statico Esterno Implicito e quindi ridondante per oggetti definiti nell'ambito del file che hanno anche un inizializzatore. Se utilizzato in una dichiarazione sullo scope del file senza un inizializzatore, suggerisce che la definizione deve essere trovata in un'altra unità di traduzione e verrà risolta al momento del collegamento.
auto Automatico non pertinente Implicito e quindi ridondante per oggetti dichiarati a scopo di blocco.
register Automatico non pertinente Rilevante solo per gli oggetti con durata di archiviazione automatica. Fornisce un suggerimento per cui la variabile deve essere memorizzata in un registro. Un vincolo imposto è che non è possibile utilizzare l'operatore unario & "indirizzo di" su tale oggetto e pertanto l'oggetto non può essere sottoposto a aliasing.
typedef non pertinente non pertinente Non è un identificatore di classe di archiviazione in pratica, ma funziona come uno da un punto di vista sintattico. L'unica differenza è che l'identificatore dichiarato è un tipo, piuttosto che un oggetto.
_Thread_local Filo Interno esterno Introdotto in C11, per rappresentare la durata della memorizzazione dei thread . Se utilizzato a livello di blocco, deve includere anche extern o static .

Ogni oggetto ha una durata di archiviazione associata (indipendentemente dall'ambito) e un collegamento (rilevante solo per le dichiarazioni nell'ambito del file), anche quando queste parole chiave vengono omesse.

L'ordine degli specificatori della classe di memoria rispetto agli identificatori di tipo di primo livello ( int , unsigned , short , ecc.) E qualificatori di tipo di primo livello ( const , volatile ) non viene applicato, quindi entrambe le dichiarazioni sono valide:

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

Tuttavia, è considerata una buona pratica inserire prima gli identificatori della classe di memoria, quindi qualsiasi qualificatore di tipo, quindi lo specificatore di tipo ( void , char , int , signed long , unsigned long long , long double ...).

Non tutti gli specificatori di classe di archiviazione sono legali in un determinato ambito:

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;

Durata di archiviazione

La durata dell'archiviazione può essere statica o automatica. Per un oggetto dichiarato, viene determinato in base all'ambito e agli identificatori della classe di archiviazione.

Durata dell'archiviazione statica

Le variabili con durata dell'archiviazione statica vivono durante l'intera esecuzione del programma e possono essere dichiarate sia nell'ambito di file (con o senza static ) sia nell'ambito di un blocco (mettendo static esplicito). Solitamente vengono allocati e inizializzati dal sistema operativo all'avvio del programma e recuperati al termine del processo. In pratica, i formati eseguibili hanno sezioni dedicate per tali variabili ( data , bss e rodata ) e queste intere sezioni del file vengono mappate in memoria in determinati intervalli.

Durata della memorizzazione del thread

C11

Questa durata di archiviazione è stata introdotta in C11. Questo non era disponibile negli standard C precedenti. Alcuni compilatori forniscono un'estensione non standard con semantica simile. Ad esempio, gcc supporta l' __thread che può essere utilizzato negli standard C precedenti che non avevano _Thread_local .

Le variabili con durata di memorizzazione del thread possono essere dichiarate sia nell'ambito del file che nell'ambito del blocco. Se dichiarato a livello di blocco, deve anche utilizzare static extern memorizzazione static o extern . La sua vita è l'intera esecuzione del thread in cui è stato creato. Questo è l'unico identificatore di archiviazione che può apparire accanto a un altro identificatore di memorizzazione.

Durata archiviazione automatica

Le variabili con durata di memorizzazione automatica possono essere dichiarate solo a livello di blocco (direttamente all'interno di una funzione o all'interno di un blocco in quella funzione). Sono utilizzabili solo nel periodo tra l'entrata e l'uscita dalla funzione o dal blocco. Una volta che la variabile esce dall'ambito (o ritornando dalla funzione o abbandonando il blocco), la sua memoria viene automaticamente deallocata. Qualsiasi ulteriore riferimento alla stessa variabile dai puntatori non è valido e porta a un comportamento non definito.

Nelle implementazioni tipiche, le variabili automatiche si trovano a determinati offset nel frame dello stack di una funzione o nei registri.

Collegamento esterno e interno

Il collegamento è rilevante solo per gli oggetti (funzioni e variabili) dichiarati nell'ambito del file e influisce sulla loro visibilità su diverse unità di traduzione. Gli oggetti con collegamento esterno sono visibili in ogni altra unità di traduzione (a condizione che sia inclusa la dichiarazione appropriata). Gli oggetti con collegamento interno non sono esposti ad altre unità di traduzione e possono essere utilizzati solo nell'unità di traduzione dove sono definiti.

typedef

Definisce un nuovo tipo basato su un tipo esistente. La sua sintassi rispecchia quella di una dichiarazione di variabile.

/* 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);

Pur non essendo tecnicamente una classe di archiviazione, un compilatore lo tratterà come uno poiché nessuna delle altre classi di memoria è consentita se viene utilizzata la parola chiave typedef .

I typedef sono importanti e non dovrebbero essere sostituiti con la macro #define .

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

Però,

#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

Questa classe di memoria indica che un identificatore ha una durata di archiviazione automatica. Ciò significa che una volta terminato lo scope in cui è stato definito l'identificatore, l'oggetto denotato dall'identificatore non è più valido.

Poiché tutti gli oggetti, che non vivono in ambito globale o sono dichiarati static , hanno una durata di archiviazione automatica per impostazione predefinita quando definita, questa parola chiave è per lo più di interesse storico e non deve essere utilizzata:

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. */

statico

La classe di archiviazione static scopi diversi, a seconda della posizione della dichiarazione nel file:

  1. Per limitare l'identificatore solo a quell'unità di traduzione (scope = file).

    /* 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. Per salvare i dati da utilizzare con la prossima chiamata di una funzione (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;
     }
    

    Questo codice stampa:

     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
    

Le variabili statiche mantengono il loro valore anche se chiamate da più thread differenti.

C99
  1. Si prevede che nei parametri di funzione per indicare che un array abbia un numero minimo costante di elementi e un parametro non nullo:

    /* 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]);
    }
    

    Il numero richiesto di elementi (o anche un puntatore non nullo) non è necessariamente controllato dal compilatore, e i compilatori non sono tenuti a notificare in alcun modo se non si dispone di elementi sufficienti. Se un programmatore supera meno di 512 elementi o un puntatore nullo, il risultato è un comportamento non definito. Poiché è impossibile applicarlo, è necessario prestare maggiore attenzione quando si passa un valore per tale parametro a tale funzione.

extern

Usato per dichiarare un oggetto o una funzione che è definita altrove (e che ha un collegamento esterno ). In generale, viene utilizzato per dichiarare un oggetto o una funzione da utilizzare in un modulo che non è quello in cui è definito l'oggetto o la funzione corrispondente:

/* 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

Le cose si fanno leggermente più interessanti con l'introduzione della parola chiave inline in 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);

Registrare

Suggerimenti per il compilatore che l'accesso a un oggetto dovrebbe essere il più veloce possibile. Se il compilatore utilizza effettivamente il suggerimento è definito dall'implementazione; può semplicemente trattarlo come equivalente ad auto .

L'unica proprietà che è definitivamente diversa per tutti gli oggetti dichiarati con il register è che non è possibile calcolare il loro indirizzo. In tal modo il register può essere un buon strumento per garantire determinate ottimizzazioni:

register size_t size = 467;

è un oggetto che non può mai alias perché nessun codice può passare il suo indirizzo a un'altra funzione in cui potrebbe essere cambiato in modo imprevisto.

Questa proprietà implica anche una matrice

register int array[5];

non può decadere in un puntatore al suo primo elemento (cioè l' array diventa &array[0] ). Ciò significa che non è possibile accedere agli elementi di tale array e che lo stesso array non può essere passato a una funzione.

In effetti, l'unico utilizzo legale di un array dichiarato con una classe di archiviazione del register è l'operatore sizeof ; qualsiasi altro operatore richiederebbe l'indirizzo del primo elemento dell'array. Per questo motivo, gli array in genere non dovrebbero essere dichiarati con la parola chiave register poiché li rende inutili per qualcosa di diverso dal calcolo della dimensione dell'intero array, il che può essere fatto altrettanto facilmente senza la parola chiave register .

La classe di archiviazione del register è più appropriata per le variabili definite all'interno di un blocco e accessibili con alta frequenza. Per esempio,

/* 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'operatore _Alignof può anche essere utilizzato con register array di register .

_Thread_local

C11

Questo era un nuovo identificatore di memoria introdotto in C11 insieme al multi-threading. Questo non è disponibile negli standard C precedenti.

Indica la durata della memorizzazione del thread . Una variabile dichiarata con lo specificatore di memoria _Thread_local denota che l'oggetto è locale a quel thread e la sua durata è l'intera esecuzione del thread in cui è stato creato. Può anche apparire insieme a static o 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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow