Clases de almacenamiento

Una clase de almacenamiento en el contexto de declaraciones de variable de C++ es un especificador de tipo que rige la ubicación de memoria, la vinculación y la duración de objetos. Un objeto determinado puede tener solo una clase de almacenamiento. Las variables definidas dentro de un bloque tienen almacenamiento automático, a menos que se especifique lo contrario con los especificadores extern, static o thread_local. Las variables y objetos automáticos no tienen ninguna vinculación; no son visibles para código fuera del bloque. La memoria se les asigna automáticamente cuando la ejecución entra en el bloque y se desasigna cuando se sale del bloque.

Notas

  • La palabra clave mutable puede considerarse un especificador de clase de almacenamiento. Sin embargo, solo está disponible en la lista de miembros de una definición de clase.

  • Visual Studio 2010 y versiones posteriores: la palabra clave auto ya no es un especificador de clase de almacenamiento de C++ y la palabra clave register está en desuso. Visual Studio 2017, versión 15.7 y posteriores: (disponible en modo /std:c++17 y posteriores): la palabra clave register se quita del lenguaje C++. Su uso provoca un mensaje de diagnóstico:

    // c5033.cpp
    // compile by using: cl /c /std:c++17 c5033.cpp
    register int value; // warning C5033: 'register' is no longer a supported storage class
    

static

La palabra clave static puede usarse para declarar variables y funciones en el ámbito global, el ámbito de espacio de nombres y el ámbito de clase. También se pueden declarar variables estáticas en el ámbito local.

Duración estática significa que el objeto o la variable se asignan cuando se inicia el programa y se desasignan cuando finaliza el programa. Vinculación externa significa que el nombre de la variable puede verse desde fuera del archivo en el que se declara la variable. A la inversa, la vinculación interna significa que el nombre no es visible fuera del archivo en el que se declara la variable. De forma predeterminada, una variable o un objeto que se defina en el espacio de nombres global tiene duración estática y vinculación externa. La palabra clave static se puede usar en las situaciones siguientes.

  1. Cuando se declara una variable o función en el ámbito de archivo (ámbito global o de espacio de nombres), la palabra clave static especifica que la variable o función tienen vinculación interna. Cuando se declara una variable, la variable tiene duración estática y el compilador la inicializa como 0, a menos que se especifique otro valor.

  2. Cuando se declara una variable en una función, la palabra clave static especifica que la variable mantiene su estado entre las llamadas a esa función.

  3. Al declarar un miembro de datos en una declaración de clase, la palabra clave static especifica que todas las instancias de la clase compartan una copia del miembro. Un miembro de datos static se debe definir en el ámbito de archivo. Un miembro de datos entero que se declara como const static puede tener un inicializador.

  4. Cuando se declara una función miembro en una declaración de clase, la palabra clave static especifica que todas las instancias de la clase comparten la función. Una función miembro static no puede acceder a un miembro de instancia porque la función no tiene un puntero this implícito. Para acceder a un miembro de instancia, declare la función con un parámetro que sea una referencia o un puntero de instancia.

  5. No se pueden declarar los miembros de union como static. Sin embargo, una variable anónima declarada globalmente union se debe declarar static de manera explícita.

En este ejemplo, se muestra cómo una variable declarada static en una función conserva su estado entre las llamadas a esa función.

// static1.cpp
// compile with: /EHsc
#include <iostream>

using namespace std;
void showstat( int curr ) {
   static int nStatic;    // Value of nStatic is retained
                          // between each function call
   nStatic += curr;
   cout << "nStatic is " << nStatic << endl;
}

int main() {
   for ( int i = 0; i < 5; i++ )
      showstat( i );
}
nStatic is 0
nStatic is 1
nStatic is 3
nStatic is 6
nStatic is 10

En este ejemplo, se muestra el uso de static en una clase.

// static2.cpp
// compile with: /EHsc
#include <iostream>

using namespace std;
class CMyClass {
public:
   static int m_i;
};

int CMyClass::m_i = 0;
CMyClass myObject1;
CMyClass myObject2;

int main() {
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;

   myObject1.m_i = 1;
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;

   myObject2.m_i = 2;
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;

   CMyClass::m_i = 3;
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;
}
0
0
1
1
2
2
3
3

En el ejemplo siguiente, se muestra una variable local declarada static en una función miembro. La variable static está disponible para el programa completo; todas las instancias del tipo comparten la misma copia de la variable static.

// static3.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
struct C {
   void Test(int value) {
      static int var = 0;
      if (var == value)
         cout << "var == value" << endl;
      else
         cout << "var != value" << endl;

      var = value;
   }
};

int main() {
   C c1;
   C c2;
   c1.Test(100);
   c2.Test(100);
}
var != value
var == value

A partir de C++11, se garantiza que la inicialización de una variable local static sea segura para subprocesos. A veces, esta característica se denomina estática mágica. Sin embargo, en una aplicación con subprocesamiento múltiple todas las asignaciones posteriores deben estar sincronizadas. La característica de inicialización estática segura para subprocesos se puede deshabilitar mediante la marca /Zc:threadSafeInit- para evitar depender de CRT.

extern

Los objetos y variables declarados como extern declaran un objeto definido en otra unidad de traducción o en un ámbito envolvente como si tuvieran vinculación externa. Para más información, consulte extern y Unidades de traducción y vinculación.

thread_local (C++11)

Una variable declarada con el especificador thread_local solo es accesible en el subproceso en el que se crea. La variable se crea cuando se crea el subproceso y se destruye cuando se destruye el subproceso. Cada subproceso tiene su propia copia de la variable. En Windows, thread_local es funcionalmente equivalente al atributo __declspec( thread ) específico de Microsoft.

thread_local float f = 42.0; // Global namespace. Not implicitly static.

struct S // cannot be applied to type definition
{
    thread_local int i; // Illegal. The member must be static.
    thread_local static char buf[10]; // OK
};

void DoSomething()
{
    // Apply thread_local to a local variable.
    // Implicitly "thread_local static S my_struct".
    thread_local S my_struct;
}

Aspectos a tener en cuenta sobre el especificador thread_local:

  • Es posible que las variables locales de subprocesos inicializadas dinámicamente en archivos DLL no se inicialicen de manera correcta en todos los subprocesos de llamada. Para obtener más información, vea thread.

  • El especificado thread_local puede combinarse con static o extern.

  • Solo puede aplicar thread_local a declaraciones y definiciones de datos; thread_local no se puede usar en declaraciones o definiciones de función.

  • Solo puede especificar thread_local en elementos de datos con duración de almacenamiento estática, que incluye objetos de datos globales (static y extern), objetos estáticos locales y miembros de datos estáticos de clases. Cualquier variable local declarada thread_local es implícitamente estática si no se proporciona ninguna otra clase de almacenamiento; es decir, en el ámbito de bloque thread_local es equivalente a thread_local static.

  • Debe especificar thread_local para la declaración y la definición de un objeto local de subproceso, ya sea que la declaración y la definición se realicen en el mismo archivo o en archivos distintos.

  • No se recomienda usar variables thread_local con std::launch::async. Para más información, consulte funciones <future>.

En Windows, thread_local es funcionalmente equivalente a __declspec(thread), salvo en que *__declspec(thread)* se puede aplicar a una definición de tipo y es válido en código de C. Siempre que sea posible, use thread_local porque forma parte del estándar de C++ y, por tanto, es más portable.

registro

Visual Studio 2017, versión 15.3 y posteriores (disponible en modo /std:c++17 y posteriores): la palabra clave register ya no es una clase de almacenamiento compatible. Su uso provoca un diagnóstico. La palabra clave todavía está reservada en el estándar para su uso futuro.

   register int val; // warning C5033: 'register' is no longer a supported storage class

Ejemplo: inicialización automática frente a estática

Una variable o un objeto automático local se inicializa cada vez que el flujo de control alcanza su definición. Una variable o un objeto estático local se inicializa la primera vez que el flujo de control alcanza su definición.

Considere el ejemplo siguiente, que define una clase que registra la inicialización y la destrucción de objetos y, a continuación, define tres objetos, I1, I2 e I3:

// initialization_of_objects.cpp
// compile with: /EHsc
#include <iostream>
#include <string.h>
using namespace std;

// Define a class that logs initializations and destructions.
class InitDemo {
public:
    InitDemo( const char *szWhat );
    ~InitDemo();

private:
    char *szObjName;
    size_t sizeofObjName;
};

// Constructor for class InitDemo
InitDemo::InitDemo( const char *szWhat ) :
    szObjName(NULL), sizeofObjName(0) {
    if ( szWhat != 0 && strlen( szWhat ) > 0 ) {
        // Allocate storage for szObjName, then copy
        // initializer szWhat into szObjName, using
        // secured CRT functions.
        sizeofObjName = strlen( szWhat ) + 1;

        szObjName = new char[ sizeofObjName ];
        strcpy_s( szObjName, sizeofObjName, szWhat );

        cout << "Initializing: " << szObjName << "\n";
    }
    else {
        szObjName = 0;
    }
}

// Destructor for InitDemo
InitDemo::~InitDemo() {
    if( szObjName != 0 ) {
        cout << "Destroying: " << szObjName << "\n";
        delete szObjName;
    }
}

// Enter main function
int main() {
    InitDemo I1( "Auto I1" ); {
        cout << "In block.\n";
        InitDemo I2( "Auto I2" );
        static InitDemo I3( "Static I3" );
    }
    cout << "Exited block.\n";
}
Initializing: Auto I1
In block.
Initializing: Auto I2
Initializing: Static I3
Destroying: Auto I2
Exited block.
Destroying: Auto I1
Destroying: Static I3

En este ejemplo, se muestra cómo y cuándo se inicializan y destruyen los objetos I1, I2 y I3.

Hay varios puntos que observar sobre el programa:

  • En primer lugar, I1 e I2 se destruyen automáticamente cuando el flujo de control sale del bloque en el que están definidos.

  • En segundo lugar, en C++, no es necesario declarar objetos o variables al principio de un bloque. Además, estos objetos se inicializan solo cuando el flujo de control alcanza sus definiciones. (I2 y I3 son ejemplos de estas definiciones). La salida muestra exactamente cuándo se inicializan.

  • Por último, las variables locales estáticas como I3 conservan sus valores mientras se ejecuta el programa, pero se destruyen en cuanto este finaliza.

Consulte también

Declaraciones y definiciones