Classes de armazenamento

Uma classe de armazenamento no contexto de declarações de variáveis C++ é um especificador de tipo que controla o tempo de vida, a vinculação e a localização da memória dos objetos. Um determinado objeto pode ter apenas uma classe de armazenamento. As variáveis definidas em um bloco têm armazenamento automático, a menos que especificado de outra forma usando os especificadores extern, static ou thread_local. Objetos e variáveis automáticos não têm vinculação; eles não são visíveis para codificar fora do bloco. Para eles, a memória é alocada automaticamente quando a execução entra no bloco e é desalocada quando o bloco é encerrado.

Observações

  • A palavra-chave mutable pode ser considerada um especificador de classe de armazenamento. No entanto, só está disponível na lista de membros de uma definição de classe.

  • Visual Studio 2010 e posterior: a palavra-chave auto não é mais um especificador de classe de armazenamento C++ e a palavra-chave register está obsoleta. Visual Studio 2017 versão 15.7 e posterior: (disponível no modo /std:c++17 e posterior): a palavra-chave register foi removida da linguagem C++. Seu uso gera uma mensagem 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

A palavra-chave static pode ser usada para declarar variáveis e funções em escopo global, escopo de namespace e escopo de classe. Variáveis estáticas também podem ser declaradas no escopo local.

Duração estática significa que o objeto ou a variável são alocados quando o programa inicia e desalocados quando o programa termina. Vinculação externa significa que o nome da variável é visível de fora do arquivo onde a variável é declarada. Por sua vez, a vinculação interna significa que o nome não é visível fora do arquivo onde a variável é declarada. Por padrão, um objeto ou variável definido no namespace global tem duração estática e vinculação externa. A palavra-chave static pode ser usada nas seguintes situações.

  1. Ao declarar uma variável ou função no escopo do arquivo (escopo global e/ou de namespace), a palavra-chave static especifica que a variável ou função possui vinculação interna. Ao declarar uma variável, a variável tem duração estática, e o compilador a inicializa com o valor 0 a menos que você especifique outro valor.

  2. Quando você declara uma variável em uma função, a palavra-chave static especifica que a variável retém seu estado entre as chamadas para essa função.

  3. Quando você declara um membro de dados em uma declaração de classe, a palavra-chave static especifica que uma cópia do membro é compartilhada por todas as instâncias da classe. Um membro de dados static deve ser definido no escopo do arquivo. Um membro de dados integral que você declara como const static pode ter um inicializador.

  4. Quando você declara uma função membro em uma declaração de classe, a palavra-chave static especifica que a função é compartilhada por todas as instâncias da classe. Uma função membro static não pode acessar um membro de instância porque a função não tem um ponteiro this implícito. Para acessar um membro de instância, declare a função com um parâmetro que seja um ponteiro ou referência de instância.

  5. Você não pode declarar os membros de um union como static. No entanto, uma union anônima declarada globalmente deve ser declarada explicitamente como static.

Este exemplo mostra como uma variável declarada static em uma função mantém seu estado entre chamadas para essa função.

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

Este exemplo mostra o uso de static em uma classe.

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

O exemplo a seguir mostra uma variável local declarada static em uma função membro. A variável static está disponível para todo o programa; todas as instâncias do tipo compartilham a mesma cópia da variável 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 do C++11, uma inicialização de variável local static é garantida como thread-safe. Esse recurso às vezes é chamado de estática mágica. No entanto, em um aplicativo multithread, todas as atribuições subsequentes devem ser sincronizadas. O recurso de inicialização estática thread-safe pode ser desabilitado usando o sinalizador /Zc:threadSafeInit- para evitar a dependência do CRT.

extern

Objetos e variáveis declarados como extern declaram um objeto definido em outra unidade de tradução ou em um escopo delimitador como tendo vinculação externa. Para obter mais informações, consulte extern e Unidades de tradução e vinculação.

thread_local (C++11)

Uma variável declarada com o especificador thread_local é acessível apenas no thread em que foi criada. A variável é criada quando o thread é criado e é destruída quando o thread é destruído. Cada thread tem sua própria cópia da variável. No Windows, thread_local é funcionalmente equivalente ao atributo __declspec( thread ) específico da 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;
}

Observações sobre o especificador thread_local:

  • Variáveis locais de thread inicializadas dinamicamente em DLLs podem não ser inicializadas corretamente em todos os threads de chamada. Para obter mais informações, consulte thread.

  • O especificador thread_local pode ser combinado com static ou extern.

  • Você pode aplicar thread_local somente a declarações e definições de dados; thread_local não pode ser usado em declarações ou definições de função.

  • Você pode especificar thread_local apenas em itens de dados com duração de armazenamento estático, que inclui objetos de dados globais (tanto static quanto extern), objetos estáticos locais e membros de dados estáticos de classes. Qualquer variável local declarada thread_local é implicitamente estática se nenhuma outra classe de armazenamento for fornecida; em outras palavras, no escopo do bloco thread_local é equivalente a thread_local static.

  • Você deve especificar thread_local para a declaração e a definição de um objeto local de thread, se a declaração e a definição ocorrerem no mesmo arquivo ou em arquivos separados.

  • Não recomendamos que você use variáveis de thread_local com std::launch::async. Para obter mais informações, consulte <future> funções.

No Windows, thread_local é funcionalmente equivalente a __declspec(thread), exceto que *__declspec(thread)* pode ser aplicado a uma definição de tipo e é válido em código C. Sempre que possível, use thread_local porque faz parte do padrão C++ e, portanto, é mais portátil.

register

Visual Studio 2017 versão 15.3 e posterior (disponível no modo /std:c++17 e posterior): a palavra-chave register não é mais uma classe de armazenamento compatível. Seu uso causa um diagnóstico. A palavra-chave ainda está reservada no padrão para uso futuro.

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

Exemplo: inicialização automática vs. estática

Um objeto ou uma variável local automática são inicializados cada vez que o fluxo de controle alcança sua definição. Um objeto ou uma variável local estática são inicializados na primeira vez que o fluxo de controle alcança sua definição.

Considere o exemplo a seguir, que define uma classe que registra a inicialização e a destruição de objetos e depois define três 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

Este exemplo demonstra como e quando os objetos I1, I2 e I3 são inicializados e quando são destruídos.

Há diversos pontos a serem observados sobre o programa:

  • Primeiro, I1 e I2 são destruídos automaticamente quando o fluxo de controle sai do bloco no qual estão definidos.

  • Segundo, em C++, não é necessário declarar objetos ou variáveis no início de um bloco. Além disso, esses objetos são inicializados somente quando o fluxo de controle atinge suas definições. (I2 e I3 são exemplos de tais definições). A saída mostra exatamente quando eles são inicializados.

  • Finalmente, variáveis locais estáticas como I3 retêm seus valores enquanto o programa é executado, mas são destruídas quando o programa termina.

Confira também

Declarações e definições