Buscar..


Introducción

Una clase de almacenamiento se usa para establecer el alcance de una variable o función. Al conocer la clase de almacenamiento de una variable, podemos determinar el tiempo de vida de esa variable durante el tiempo de ejecución del programa.

Sintaxis

  • [auto | register | static | extern] <Tipo de datos> <Nombre de variable> [= <Valor>];

  • [static _Thread_local | extern _Thread_local | _Thread_local] <Tipo de datos> <Nombre de variable> [= <Valor>]; / * desde> = C11 * /

  • Ejemplos:

  • typedef int foo ;

  • extern int foo [2];

Observaciones

Los especificadores de clase de almacenamiento son las palabras clave que pueden aparecer junto al tipo de declaración de nivel superior. El uso de estas palabras clave afecta la duración del almacenamiento y la vinculación del objeto declarado, dependiendo de si se declara en el alcance del archivo o en el alcance del bloque:

Palabra clave Duración del almacenamiento Enlace Observaciones
static Estático Interno Establece el enlace interno para los objetos en el alcance del archivo; establece la duración del almacenamiento estático para objetos en el ámbito de bloque.
extern Estático Externo Implícito y, por lo tanto, redundante para los objetos definidos en el alcance del archivo que también tienen un inicializador. Cuando se usa en una declaración en el alcance del archivo sin un inicializador, insinúa que la definición se encuentra en otra unidad de traducción y se resolverá en el momento del enlace.
auto Automático Irrelevante Implícito y por lo tanto redundante para objetos declarados en el ámbito de bloque.
register Automático Irrelevante Relevante solo para objetos con duración de almacenamiento automático. Proporciona una pista de que la variable debe almacenarse en un registro. Una restricción impuesta es que uno no puede usar el operador unario & "dirección de" en dicho objeto, y por lo tanto el objeto no puede tener un alias.
typedef Irrelevante Irrelevante No es un especificador de clase de almacenamiento en la práctica, pero funciona como uno desde un punto de vista sintáctico. La única diferencia es que el identificador declarado es un tipo, en lugar de un objeto.
_Thread_local Hilo Interno externo Introducido en C11, para representar la duración del almacenamiento de hilos . Si se usa en el alcance del bloque, también incluirá extern o static .

Cada objeto tiene una duración de almacenamiento asociada (independientemente del alcance) y la vinculación (relevante solo para las declaraciones en el alcance del archivo), incluso cuando se omiten estas palabras clave.

El ordenamiento de los especificadores de clase de almacenamiento con respecto a los especificadores de tipo de nivel superior ( int , unsigned , short , etc.) y los calificadores de tipo de nivel superior ( const , volatile ) no se aplica, por lo que ambas declaraciones son válidas:

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

Sin embargo, se considera una buena práctica poner primero los especificadores de clase de almacenamiento, luego los calificadores de cualquier tipo, luego el especificador de tipo ( void , char , int , signed long , unsigned long long , long double ...).

No todos los especificadores de clase de almacenamiento son legales en un determinado ámbito:

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;

Duración del almacenamiento

La duración del almacenamiento puede ser estática o automática. Para un objeto declarado, se determina según su alcance y los especificadores de clase de almacenamiento.

Duración del almacenamiento estático

Las variables con duración de almacenamiento estático se mantienen activas durante toda la ejecución del programa y se pueden declarar tanto en el alcance del archivo (con o sin static ) como en el alcance del bloque (poniendo la static explícitamente). Por lo general, son asignados e inicializados por el sistema operativo al inicio del programa y se reclaman cuando finaliza el proceso. En la práctica, los formatos ejecutables tienen secciones dedicadas para dichas variables ( data , bss y rodata ) y estas secciones completas del archivo se asignan a la memoria en ciertos rangos.

Duración de almacenamiento de hilo

C11

Esta duración de almacenamiento se introdujo en C11. Esto no estaba disponible en los estándares C anteriores. Algunos compiladores proporcionan una extensión no estándar con semántica similar. Por ejemplo, gcc admite el especificador __thread que se puede usar en estándares C anteriores que no tenían _Thread_local .

Las variables con duración de almacenamiento de subprocesos se pueden declarar tanto en el alcance del archivo como en el alcance del bloque. Si se declara en el ámbito del bloque, también utilizará un especificador de almacenamiento static o extern . Su duración es la ejecución completa del subproceso en el que se crea. Este es el único especificador de almacenamiento que puede aparecer junto a otro especificador de almacenamiento.

Duración del almacenamiento automático

Las variables con duración de almacenamiento automático solo se pueden declarar en el alcance del bloque (directamente dentro de una función o dentro de un bloque en esa función). Solo se pueden utilizar en el período entre la entrada y la salida de la función o el bloque. Una vez que la variable queda fuera del alcance (ya sea volviendo de la función o dejando el bloque), su almacenamiento se desasigna automáticamente. Cualquier referencia adicional a la misma variable desde los punteros no es válida y conduce a un comportamiento indefinido.

En implementaciones típicas, las variables automáticas se ubican en ciertas compensaciones en el marco de pila de una función o en registros.

Enlace externo e interno

La vinculación solo es relevante para los objetos (funciones y variables) declarados en el alcance del archivo y afecta su visibilidad en diferentes unidades de traducción. Los objetos con enlace externo son visibles en todas las demás unidades de traducción (siempre que se incluya la declaración correspondiente). Los objetos con enlace interno no están expuestos a otras unidades de traducción y solo se pueden usar en la unidad de traducción donde están definidos.

typedef

Define un nuevo tipo basado en un tipo existente. Su sintaxis refleja la de una declaración 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);

Aunque técnicamente no es una clase de almacenamiento, un compilador lo tratará como uno ya que ninguna de las otras clases de almacenamiento está permitida si se usa la palabra clave typedef .

Los typedef s son importantes y no deben sustituirse con la macro #define .

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

Sin embargo,

#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

Esta clase de almacenamiento denota que un identificador tiene una duración de almacenamiento automática. Esto significa que una vez que el ámbito en el que se definió el identificador termina, el objeto denotado por el identificador ya no es válido.

Dado que todos los objetos, que no viven en el ámbito global o que se declaran static , tienen una duración de almacenamiento automático por defecto cuando se definen, esta palabra clave es en su mayoría de interés histórico y no debe usarse:

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

estático

La clase de almacenamiento static tiene diferentes propósitos, dependiendo de la ubicación de la declaración en el archivo:

  1. Para limitar el identificador a esa unidad de traducción solamente (scope = archivo).

    /* 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. Para guardar datos para usar con la próxima llamada de una función (alcance = bloque):

     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;
     }
    

    Este código 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
    

Las variables estáticas conservan su valor incluso cuando se las llama desde varios subprocesos diferentes.

C99
  1. Se utiliza en los parámetros de función para denotar que una matriz tiene un número mínimo constante de elementos y un parámetro no nulo:

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

    El compilador no comprueba necesariamente el número requerido de elementos (o incluso un puntero que no sea nulo), y los compiladores no tienen la obligación de notificarle de ninguna manera si no tiene suficientes elementos. Si un programador pasa menos de 512 elementos o un puntero nulo, el resultado es un comportamiento indefinido. Dado que es imposible hacer cumplir esto, se debe tener mucho cuidado al pasar un valor para ese parámetro a dicha función.

externo

Se utiliza para declarar un objeto o función que se define en otro lugar (y que tiene un enlace externo ). En general, se utiliza para declarar que un objeto o una función se utiliza en un módulo que no es aquel en el que se define el objeto o función correspondiente:

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

Las cosas se ponen un poco más interesantes con la introducción de la palabra clave en inline en 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);

registro

Indica al compilador que el acceso a un objeto debe ser lo más rápido posible. Si el compilador realmente usa la sugerencia está definido por la implementación; simplemente puede tratarlo como equivalente a auto .

La única propiedad que es definitivamente diferente para todos los objetos que se declaran con register es que no pueden calcularse sus direcciones. Por lo tanto, register puede ser una buena herramienta para asegurar ciertas optimizaciones:

register size_t size = 467;

es un objeto que nunca puede ser alias porque ningún código puede pasar su dirección a otra función en la que podría cambiarse inesperadamente.

Esta propiedad también implica que una matriz

register int array[5];

no puede descomponerse en un puntero a su primer elemento (es decir, la array convierte en &array[0] ). Esto significa que no se puede acceder a los elementos de dicha matriz y que la matriz en sí no se puede pasar a una función.

De hecho, el único uso legal de una matriz declarada con una clase de almacenamiento de register es el operador sizeof ; cualquier otro operador requeriría la dirección del primer elemento de la matriz. Por esa razón, los arreglos generalmente no deben declararse con la palabra clave de register ya que los hace inútiles para cualquier otra cosa que no sea el cálculo de tamaño de la matriz completa, lo que puede hacerse con la misma facilidad sin la palabra clave de register .

La clase de almacenamiento de register es más apropiada para las variables que se definen dentro de un bloque y se accede a ellas con alta frecuencia. Por ejemplo,

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

El operador _Alignof también se puede usar con matrices de register .

_Hilo_local

C11

Este fue un nuevo especificador de almacenamiento introducido en C11 junto con subprocesos múltiples. Esto no está disponible en los estándares C anteriores.

Indica la duración del almacenamiento de hilos . Una variable declarada con el especificador de almacenamiento _Thread_local denota que el objeto es local para ese hilo y su duración es la ejecución completa del hilo en el que se creó. También puede aparecer junto con 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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow