¿Cuál es la vida útil de una variable estática en una función C ++?

373

Si una variable se declara como staticdentro del alcance de una función, solo se inicializa una vez y conserva su valor entre las llamadas a funciones. ¿Qué es exactamente su vida útil? ¿Cuándo se llama a su constructor y destructor?

void foo() 
{ 
    static string plonk = "When will I die?";
}
Motti
fuente

Respuestas:

257

La vida útil de las staticvariables de función comienza la primera vez [0] que el flujo del programa encuentra la declaración y termina al finalizar el programa. Esto significa que el tiempo de ejecución debe llevar a cabo una contabilidad para destruirlo solo si realmente se construyó.

Además, dado que el estándar dice que los destructores de objetos estáticos deben ejecutarse en el orden inverso a la finalización de su construcción [1] , y el orden de construcción puede depender del programa específico ejecutado, el orden de construcción debe tenerse en cuenta .

Ejemplo

struct emitter {
    string str;
    emitter(const string& s) : str(s) { cout << "Created " << str << endl; }
    ~emitter() { cout << "Destroyed " << str << endl; }
};

void foo(bool skip_first) 
{
    if (!skip_first)
        static emitter a("in if");
    static emitter b("in foo");
}

int main(int argc, char*[])
{
    foo(argc != 2);
    if (argc == 3)
        foo(false);
}

Salida:

C:> sample.exe
Creado en foo
Destruido en foo

C:> sample.exe 1
Creado en if
Creado en foo
Destruido en foo
Destruido en if

C:> sample.exe 1 2
Creado en foo
Creado en if
Destroyed en if
Destroyed en foo

[0]Dado que C ++ 98 [2] no hace referencia a múltiples subprocesos, cómo se comportará esto en un entorno de subprocesos múltiples no está especificado y puede ser problemático como menciona Roddy .

[1] Sección C ++ 98 3.6.3.1 [basic.start.term]

[2]En C ++ 11, las estadísticas se inicializan de manera segura para subprocesos, esto también se conoce como Magic Statics .

Motti
fuente
2
Para los tipos simples sin efectos secundarios de c'tor / d'tor, es una optimización directa inicializarlos de la misma manera que los tipos simples globales. Esto evita los problemas de ramificación, bandera y orden de destrucción. Eso no quiere decir que su vida sea diferente.
John McFarlane
1
Si la función puede ser invocada por múltiples subprocesos, ¿significa esto que debe asegurarse de que las declaraciones estáticas deben estar protegidas por un mutex en C ++ 98?
allyourcode
1
"los destructores de objetos globales deben ejecutarse en el orden inverso de la finalización de su construcción" no se aplica aquí, porque estos objetos no son globales. El orden de destrucción de los locales con una duración de almacenamiento estático o de subprocesos es considerablemente más complicado que el LIFO puro, consulte la sección 3.6.3[basic.start.term]
Ben Voigt
2
La frase "al finalizar el programa" no es estrictamente correcta. ¿Qué pasa con las estadísticas en Windows DLL que se cargan y descargan dinámicamente? Obviamente, el estándar C ++ no se ocupa de los ensambles en absoluto (sería bueno si lo hiciera), pero una aclaración sobre exactamente qué dice el estándar aquí sería bueno. Si se incluyera la frase "al final del programa", técnicamente haría que cualquier implementación de C ++ con ensamblajes dinámicamente descargados no fuera conforme.
Roger Sanders
2
@Motti No creo que el estándar permita explícitamente bibliotecas dinámicas, pero hasta ahora tampoco creía que hubiera algo específicamente en el estándar que estuviera en desacuerdo con su implementación. Por supuesto, estrictamente hablando, el lenguaje aquí no establece que los objetos estáticos no se puedan destruir antes por otros medios, solo que deben destruirse al regresar de main o llamar a std :: exit. Una línea muy fina, aunque creo.
Roger Sanders
125

Motti tiene razón sobre el orden, pero hay algunas otras cosas a considerar:

Los compiladores suelen utilizar una variable de marca oculta para indicar si las estadísticas locales ya se han inicializado, y esta marca se verifica en cada entrada a la función. Obviamente, este es un pequeño éxito en el rendimiento, pero lo que es más preocupante es que no se garantiza que este indicador sea seguro para subprocesos.

Si tiene una estática local como la anterior y foose llama desde varios subprocesos, es posible que las condiciones de carrera provoquen plonkque se inicialicen de forma incorrecta o incluso varias veces. Además, en este caso plonkpuede ser destruido por un hilo diferente al que lo construyó.

A pesar de lo que dice el estándar, desconfiaría del orden real de destrucción estática local, porque es posible que, sin saberlo, pueda confiar en que una estática sigue siendo válida después de que se haya destruido, y esto es realmente difícil de rastrear.

Roddy
fuente
68
C ++ 0x requiere que la inicialización estática sea segura para subprocesos. Así que ten cuidado, pero las cosas solo mejorarán.
deft_code
Los problemas de orden de destrucción se pueden evitar con una pequeña política. Los objetos estáticos / globales (singletons, etc.) no deben acceder a otros objetos estáticos en sus cuerpos de método. Solo se accederá en constructores donde se pueda almacenar una referencia / puntero para acceder posteriormente en métodos. Esto no es perfecto, pero debería solucionar 99 de los casos y los casos que no detecta son obviamente sospechosos y deben detectarse en una revisión de código. Esto todavía no es una solución perfecta ya que la política no se puede aplicar en el idioma
deft_code
Soy un poco novato, pero ¿por qué no se puede hacer cumplir esta política en el idioma?
cjcurrie
99
Desde C ++ 11, esto ya no es un problema. La respuesta de Motti se actualiza de acuerdo con eso.
Nilanjan Basu
10

Las explicaciones existentes no están realmente completas sin la regla real del Estándar, que se encuentra en 6.7:

La inicialización cero de todas las variables de ámbito de bloque con duración de almacenamiento estático o duración de almacenamiento de subprocesos se realiza antes de que tenga lugar cualquier otra inicialización. La inicialización constante de una entidad de alcance de bloque con una duración de almacenamiento estático, si corresponde, se realiza antes de que su bloque se ingrese por primera vez. Se permite que una implementación realice una inicialización temprana de otras variables de alcance de bloque con una duración de almacenamiento estático o de subprocesos en las mismas condiciones en que una implementación puede inicializar estáticamente una variable con una duración de almacenamiento estático o de subproceso en el alcance del espacio de nombres. De lo contrario, dicha variable se inicializa la primera vez que el control pasa por su declaración; dicha variable se considera inicializada 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 volverá a intentar la próxima vez que el control ingrese la declaración. Si el control ingresa la declaración simultáneamente mientras se inicializa la variable, la ejecución concurrente esperará a que se complete la inicialización. Si el control vuelve a ingresar la declaración de forma recursiva mientras se inicializa la variable, el comportamiento es indefinido.

Ben Voigt
fuente
8

FWIW, Codegear C ++ Builder no se destruye en el orden esperado de acuerdo con el estándar.

C:\> sample.exe 1 2
Created in foo
Created in if
Destroyed in foo
Destroyed in if

... que es otra razón para no confiar en la orden de destrucción!

Roddy
fuente
57
No es un buen argumento. Yo diría que este es más un argumento para no usar este compilador.
Martin York
26
Hmm Si está interesado en producir código portátil del mundo real, en lugar de solo código teóricamente portátil, creo que es útil saber qué áreas del lenguaje pueden causar problemas. Me sorprendería si C ++ Builder fuera único al no manejar esto.
Roddy
17
Estoy de acuerdo, excepto que lo expresaría como "qué compiladores causan problemas y en qué áreas del idioma lo hacen" ;-P
Steve Jessop
0

Las variables estáticas entran en juego una vez que comienza la ejecución del programa y permanecen disponibles hasta que finaliza la ejecución del programa.

Las variables estáticas se crean en el segmento de datos de la memoria .

Chandra Shekhar
fuente
esto no es cierto para las variables en el alcance de la función
awerries