¿Cuándo se asignan / inicializan las variables estáticas a nivel de función?

90

Estoy bastante seguro de que las variables declaradas globalmente se asignan (e inicializan, si corresponde) en el momento de inicio del programa.

int globalgarbage;
unsigned int anumber = 42;

Pero, ¿qué pasa con los estáticos definidos dentro de una función?

void doSomething()
{
  static bool globalish = true;
  // ...
}

¿Cuándo se globalishasigna el espacio ? Supongo que cuando comienza el programa. ¿Pero también se inicializa entonces? ¿O se inicializa cuando doSomething()se llama por primera vez?

Owen
fuente

Respuestas:

92

Tenía curiosidad por esto, así que escribí el siguiente programa de prueba y lo compilé con la versión 4.1.2 de g ++.

include <iostream>
#include <string>

using namespace std;

class test
{
public:
        test(const char *name)
                : _name(name)
        {
                cout << _name << " created" << endl;
        }

        ~test()
        {
                cout << _name << " destroyed" << endl;
        }

        string _name;
};

test t("global variable");

void f()
{
        static test t("static variable");

        test t2("Local variable");

        cout << "Function executed" << endl;
}


int main()
{
        test t("local to main");

        cout << "Program start" << endl;

        f();

        cout << "Program end" << endl;
        return 0;
}

Los resultados no fueron los que esperaba. No se llamó al constructor del objeto estático hasta la primera vez que se llamó a la función. Aquí está el resultado:

global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed
Adam Pierce
fuente
30
Como aclaración: la variable estática se inicializa la primera vez que la ejecución alcanza su declaración, no cuando se llama a la función contenedora. Si solo tiene una estática al comienzo de la función (por ejemplo, en su ejemplo), estos son los mismos, pero no necesariamente: por ejemplo, si tiene 'if (...) {static MyClass x; ...} ', entonces' x 'no se inicializará en TODOS durante la primera ejecución de esa función en el caso de que la condición de la instrucción if se evalúe como falsa.
EvanED
4
Pero, ¿esto no conlleva una sobrecarga de tiempo de ejecución, ya que cada vez que se utiliza la variable estática, el programa tiene que comprobar si se ha utilizado previamente, ya que si no, hay que inicializarla? En ese caso, eso apesta un poco.
Hola
ilustración perfecta
Des1gnWizard
@veio: Sí, la inicialización es segura para subprocesos. Consulte esa pregunta para obtener más detalles: stackoverflow.com/questions/23829389/…
Rémi
2
@HelloGoodbye: sí, genera una sobrecarga de tiempo de ejecución. También vea esa pregunta: stackoverflow.com/questions/23829389/…
Rémi
53

Algunas palabras relevantes del estándar C ++:

3.6.2 Inicialización de objetos no locales [basic.start.init]

1

El almacenamiento de objetos con duración de almacenamiento estático ( basic.stc.static ) deberá inicializarse a cero ( dcl.init ) antes de que tenga lugar cualquier otra inicialización. Los objetos de tipos POD ( basic.types ) con una duración de almacenamiento estático inicializados con expresiones constantes ( expr.const ) se inicializarán antes de que tenga lugar cualquier inicialización dinámica. Los objetos de ámbito de espacio de nombres con una duración de almacenamiento estático definidos en la misma unidad de traducción e inicializados dinámicamente se inicializarán en el orden en que su definición aparece en la unidad de traducción. [Nota: dcl.init.aggr describe el orden en el que se inicializan los miembros agregados. La inicialización de objetos estáticos locales se describe en stmt.dcl . ]

[más texto a continuación agregando más libertades para los redactores del compilador]

6.7 Declaración de declaración [stmt.dcl]

...

4

La inicialización cero ( dcl.init ) de todos los objetos locales con duración de almacenamiento estático ( basic.stc.static ) se realiza antes de que tenga lugar cualquier otra inicialización. Un objeto local de tipo POD ( basic.types ) con una duración de almacenamiento estático inicializado con expresiones constantes se inicializa antes de que se ingrese por primera vez en su bloque. Se permite que una implementación realice la inicialización anticipada de otros objetos locales con duración de almacenamiento estático en las mismas condiciones en que se permite una implementación para inicializar estáticamente un objeto con duración de almacenamiento estático en el ámbito del espacio de nombres ( basic.start.init ). De lo contrario, dicho objeto se inicializa la primera vez que el control pasa por su declaración; dicho objeto se considera inicializado una vez completada su inicialización. Si la inicialización sale lanzando una excepción, la inicialización no está completa, por lo que se intentará nuevamente la próxima vez que el control ingrese a la declaración. Si el control vuelve a ingresar la declaración (recursivamente) mientras se inicializa el objeto, el comportamiento es indefinido. [ Ejemplo:

      int foo(int i)
      {
          static int s = foo(2*i);  // recursive call - undefined
          return i+1;
      }

- ejemplo final ]

5

El destructor de un objeto local con una duración de almacenamiento estático se ejecutará si y solo si se construyó la variable. [Nota: basic.start.term describe el orden en el que se destruyen los objetos locales con una duración de almacenamiento estático. ]

Jason Plank
fuente
Esto respondió a mi pregunta y no se basa en "pruebas anecdóticas" a diferencia de la respuesta aceptada. Estaba buscando específicamente esta mención de excepciones en el constructor de objetos estáticos locales de funciones inicializadas estáticamente:If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration.
Bensge
26

La memoria para todas las variables estáticas se asigna al cargar el programa. Pero las variables estáticas locales se crean e inicializan la primera vez que se utilizan, no al iniciar el programa. Hay una buena lectura sobre eso, y estática en general, aquí . En general, creo que algunos de estos problemas dependen de la implementación, especialmente si desea saber en qué parte de la memoria se ubicarán estas cosas.

Eugenio
fuente
2
no del todo, las estáticas locales se asignan y se inicializan en cero "al cargar el programa" (entre comillas, porque tampoco es del todo correcto), y luego se reinicializan la primera vez que se ingresa la función en la que se encuentran.
Mooing Duck
Parece que ese vínculo ahora está roto, 7 años después.
Steve
1
Sí, el enlace se rompió. Aquí hay un archivo: web.archive.org/web/20100328062506/http://www.acm.org/…
Eugene
10

El compilador asignará variables estáticas definidas en una función fooal cargar el programa, sin embargo, el compilador también agregará algunas instrucciones adicionales (código de máquina) a su función foopara que la primera vez que se invoque este código adicional inicializará la variable estática ( por ejemplo, invocando al constructor, si corresponde).

@Adam: Esta inyección de código detrás de escena por parte del compilador es la razón del resultado que vio.

Henk
fuente
5

Intento probar nuevamente el código de Adam Pierce y agregué dos casos más: variable estática en clase y tipo POD. Mi compilador es g ++ 4.8.1, en el sistema operativo Windows (MinGW-32). El resultado es una variable estática en la clase que se trata igual que la variable global. Su constructor será llamado antes de ingresar a la función principal.

  • Conclusión (para g ++, entorno Windows):

    1. Variable global y miembro estático en la clase : se llama al constructor antes de ingresar la función principal (1) .
    2. Variable estática local : solo se llama al constructor cuando la ejecución alcanza su declaración por primera vez.
    3. Si la variable estática local es del tipo POD , también se inicializa antes de ingresar la función principal (1) . Ejemplo de tipo POD: static int number = 10;

(1) : El estado correcto debe ser: "antes de que se llame a cualquier función de la misma unidad de traducción". Sin embargo, por simple, como en el ejemplo siguiente, entonces es la función principal .

incluir <iostream>

#include < string>

using namespace std;

class test
{
public:
   test(const char *name)
            : _name(name)
    {
            cout << _name << " created" << endl;
    }

    ~test()
    {
            cout << _name << " destroyed" << endl;
    }

    string _name;
    static test t; // static member
 };
test test::t("static in class");

test t("global variable");

void f()
{
    static  test t("static variable");
    static int num = 10 ; // POD type, init before enter main function

    test t2("Local variable");
    cout << "Function executed" << endl;
}

int main()
{
    test t("local to main");
    cout << "Program start" << endl;
    f();
    cout << "Program end" << endl;
    return 0;
 }

resultado:

static in class created
global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed
static in class destroyed

¿Alguien probado en Linux env?

Thang Le
fuente
4

¿O se inicializa cuando se llama por primera vez a doSomething ()?

Sí lo es. Esto, entre otras cosas, le permite inicializar estructuras de datos de acceso global cuando sea apropiado, por ejemplo, dentro de bloques try / catch. Por ejemplo, en lugar de

int foo = init(); // bad if init() throws something

int main() {
  try {
    ...
  }
  catch(...){
    ...
  }
}

puedes escribir

int& foo() {
  static int myfoo = init();
  return myfoo;
}

y utilícelo dentro del bloque try / catch. En la primera llamada, la variable se inicializará. Luego, en la primera y la siguiente llamada, se devolverá su valor (por referencia).

dmityugov
fuente
3

Las variables estáticas se asignan dentro de un segmento de código; son parte de la imagen ejecutable y, por lo tanto, se asignan ya inicializadas.

Las variables estáticas dentro del alcance de la función se tratan de la misma manera, el alcance es puramente una construcción de nivel de lenguaje.

Por esta razón, tiene la garantía de que una variable estática se inicializará en 0 (a menos que especifique algo más) en lugar de un valor indefinido.

Hay algunas otras facetas de la inicialización que puede aprovechar; por ejemplo, los segmentos compartidos permiten que diferentes instancias de su ejecutable se ejecuten a la vez para acceder a las mismas variables estáticas.

En C ++ (de ámbito global) los objetos estáticos tienen sus constructores llamados como parte del inicio del programa, bajo el control de la biblioteca de tiempo de ejecución de C. En Visual C ++, al menos el orden en el que se inicializan los objetos se puede controlar mediante el pragma init_seg .

Rob Walker
fuente
4
Esta pregunta trata sobre la estática con ámbito de función. Al menos cuando tienen constructores no triviales, se inicializan en la primera entrada a la función. O más específicamente, cuando se alcanza esa línea.
Adam Mitz
Es cierto, pero la pregunta habla sobre el espacio asignado a la variable y utiliza tipos de datos simples. El espacio todavía está asignado en el segmento de código
Rob Walker
No veo cómo el segmento de código frente al segmento de datos realmente importa aquí. Creo que necesitamos una aclaración del PO. Dijo "e inicializado, si corresponde".
Adam Mitz
5
las variables nunca se asignan dentro del segmento de código; de esta forma, no se podrán escribir.
botismarius
1
a las variables estáticas se les asigna espacio en el segmento de datos o en el segmento bss, dependiendo de si están inicializadas o no.
EmptyData