Declaraciones de variables en archivos de encabezado: ¿estático o no?

91

Al refactorizar algunos #defines, encontré declaraciones similares a las siguientes en un archivo de encabezado de C ++:

static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;

La pregunta es, ¿qué diferencia, si la hay, hará la estática? Tenga en cuenta que la inclusión múltiple de los encabezados no es posible debido al #ifndef HEADER #define HEADER #endiftruco clásico (si eso importa).

¿Significa estática que solo VALse crea una copia de , en caso de que el encabezado esté incluido en más de un archivo fuente?

Robar
fuente
relacionado: stackoverflow.com/questions/177437/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

107

Esto staticsignifica que habrá una copia de VALcreado para cada archivo fuente en el que se incluye. Pero también significa que múltiples inclusiones no darán como resultado múltiples definiciones VALque colisionarán en el momento del enlace. En C, sin el static, necesitaría asegurarse de que solo un archivo VALfuente esté definido mientras que los otros archivos fuente lo declararon extern. Por lo general, uno haría esto definiéndolo (posiblemente con un inicializador) en un archivo fuente y colocando la externdeclaración en un archivo de encabezado.

static las variables a nivel global solo son visibles en su propio archivo fuente, ya sea que hayan llegado a través de una inclusión o que estén en el archivo principal.


Nota del editor: en C ++, los constobjetos que no tienen ni las palabras clave staticni externen su declaración son implícitamente static.

Justsalt
fuente
Soy fanático de la última oración, increíblemente útil. No voté a favor de la respuesta porque 42 es mejor. editar: gramática
RealDeal_EE'18
"La estática significa que habrá una copia de VAL creada para cada archivo fuente en el que se incluye". Eso parece implicar que habría dos copias de VAL si dos archivos fuente incluyeran el archivo de encabezado. Espero que eso no sea cierto y que siempre haya una única instancia de VAL, independientemente de cuántos archivos incluyan el encabezado.
Brent212
4
@ Brent212 El compilador no sabe si una declaración / definición proviene de un archivo de encabezado o de un archivo principal. Así que esperas en vano. Habrá dos copias de VAL si alguien fue tonto y puso una definición estática en un archivo de encabezado y se incluyó en dos fuentes.
Justsalt
1
los valores const tienen enlace interno en C ++
adrianN
112

Las etiquetas staticy externen las variables de ámbito de archivo determinan si son accesibles en otras unidades de traducción (es decir, otros .co .cpparchivos).

  • staticda a la variable vinculación interna, ocultándola de otras unidades de traducción. Sin embargo, las variables con vinculación interna se pueden definir en varias unidades de traducción.

  • externle da a la variable enlace externo, haciéndola visible para otras unidades de traducción. Normalmente, esto significa que la variable solo debe definirse en una unidad de traducción.

El valor predeterminado (cuando no especifica statico extern) es una de esas áreas en las que C y C ++ difieren.

  • En C, las variables de ámbito de archivo son extern(enlace externo) de forma predeterminada. Si está utilizando C, VALes staticy ANOTHER_VALes extern.

  • En C ++, las variables de ámbito de archivo son static(enlace interno) de forma predeterminada si lo son const, y externde forma predeterminada si no lo son. Si está usando C ++, ambos VALy lo ANOTHER_VALson static.

De un borrador de la especificación C :

6.2.2 Vínculos de identificadores ... -5- Si la declaración de un identificador para una función no tiene un especificador de clase de almacenamiento, su vínculo se determina exactamente como si se declarara con el especificador de clase de almacenamiento extern. Si la declaración de un identificador para un objeto tiene alcance de archivo y ningún especificador de clase de almacenamiento, su vínculo es externo.

De un borrador de la especificación C ++ :

7.1.1 - Especificadores de clase de almacenamiento [dcl.stc] ... -6- Un nombre declarado en un ámbito de espacio de nombres sin un especificador de clase de almacenamiento tiene un vínculo externo a menos que tenga un vínculo interno debido a una declaración previa y siempre que no lo sea declarada const. Los objetos declarados const y no declarados explícitamente como extern tienen vínculos internos.

bk1e
fuente
47

La estática significará que obtendrá una copia por archivo, pero a diferencia de otros que han dicho que es perfectamente legal hacerlo. Puede probar esto fácilmente con una pequeña muestra de código:

test.h:

static int TEST = 0;
void test();

test1.cpp:

#include <iostream>
#include "test.h"

int main(void) {
    std::cout << &TEST << std::endl;
    test();
}

test2.cpp:

#include <iostream>
#include "test.h"

void test() {
    std::cout << &TEST << std::endl;
}

Ejecutar esto le da esta salida:

0x446020
0x446040

rodajas de limón
fuente
5
¡Gracias por el ejemplo!
Kyrol
Me pregunto si lo TESTfuera const, si LTO podría optimizarlo en una sola ubicación de memoria. Pero -O3 -fltode GCC 8.1 no lo hizo.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Sería ilegal que lo hiciera, incluso si es una garantía estática constante de que cada instancia es local para la unidad de compilación. Sin embargo, probablemente podría incluir el valor constante en línea si se usa como una constante, pero dado que tomamos su dirección, tiene que devolver un puntero único.
limón en rodajas
6

constlas variables en C ++ tienen vínculos internos. Entonces, usar staticno tiene ningún efecto.

ah

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

two.cpp

#include "a.h"

func1()
{
   cout << i;
}

Si se tratara de un programa en C, obtendría un error de 'definición múltiple' para i(debido a un enlace externo).

Nitin
fuente
2
Bueno, el uso statictiene el efecto de señalar claramente la intención y la conciencia de lo que se está codificando, lo cual nunca es malo. Para mí, esto es como incluir virtualcuando se anula: no tenemos que hacerlo, pero las cosas se ven mucho más intuitivas y coherentes con otras declaraciones cuando lo hacemos.
underscore_d
Es posible que obtenga un error de definición múltiple en C. Es un comportamiento indefinido sin necesidad de diagnóstico
MM
5

La declaración estática en este nivel de código significa que la variabel solo es visible en la unidad de compilación actual. Esto significa que solo el código dentro de ese módulo verá esa variable.

si tiene un archivo de encabezado que declara una variable estática y ese encabezado está incluido en varios archivos C / CPP, entonces esa variable será "local" para esos módulos. Habrá N copias de esa variable para los N lugares en los que se incluye el encabezado. No están relacionados entre sí en absoluto. Cualquier código dentro de cualquiera de esos archivos fuente solo hará referencia a la variable declarada dentro de ese módulo.

En este caso particular, la palabra clave 'estática' no parece proporcionar ningún beneficio. Puede que me esté perdiendo algo, pero parece que no importa: nunca antes había visto algo así.

En cuanto a la inserción, en este caso la variable probablemente esté integrada, pero eso es solo porque se declara const. El compilador podría ser más probable que las variables de módulo estático en línea, pero eso es depende de la situación y el código que está siendo recopilada. No hay garantía de que el compilador incorpore 'estática'.

marca
fuente
El beneficio de 'estático' aquí es que, de lo contrario, está declarando múltiples globales, todos con el mismo nombre, uno para cada módulo que incluye el encabezado. Si el enlazador no se queja es solo porque se muerde la lengua y es cortés.
En este caso, debido a const, el staticestá implícito y, por lo tanto, es opcional. El corolario es que no hay susceptibilidad a múltiples errores de definición como afirmó Mike F.
underscore_d
2

Para responder a la pregunta, "¿la estática significa que solo se crea una copia de VAL, en caso de que el encabezado esté incluido en más de un archivo fuente?" ...

NO . VAL siempre se definirá por separado en cada archivo que incluya el encabezado.

Los estándares para C y C ++ causan una diferencia en este caso.

En C, las variables de ámbito de archivo son externas de forma predeterminada. Si está utilizando C, VAL es estático y ANOTHER_VAL es externo.

Tenga en cuenta que los enlazadores modernos pueden quejarse de ANOTHER_VAL si el encabezado se incluye en archivos diferentes (el mismo nombre global se define dos veces), y definitivamente se quejarían si se inicializara ANOTHER_VAL con un valor diferente en otro archivo

En C ++, las variables de ámbito de archivo son estáticas de forma predeterminada si son constantes y externas de forma predeterminada si no lo son. Si está utilizando C ++, tanto VAL como ANOTHER_VAL son estáticos.

También debe tener en cuenta el hecho de que ambas variables se designan const. Idealmente, el compilador siempre elegiría alinear estas variables y no incluir ningún almacenamiento para ellas. Hay una gran cantidad de razones por las que se puede asignar almacenamiento. Los que puedo pensar ...

  • opciones de depuración
  • dirección tomada en el archivo
  • El compilador siempre asigna almacenamiento (los tipos const complejos no se pueden insertar fácilmente, por lo que se convierte en un caso especial para los tipos básicos)
itj
fuente
Nota: En la máquina de resúmenes hay una copia de VAL en cada unidad de traducción separada que incluye el encabezado. En la práctica, el enlazador podría decidir combinarlos de todos modos y el compilador podría optimizar algunos o todos ellos primero.
MM
1

Suponiendo que estas declaraciones tienen un alcance global (es decir, no son variables miembro), entonces:

estático significa "enlace interno". En este caso, dado que se declara const, el compilador puede optimizarlo / alinearlo. Si omite la constante , el compilador debe asignar almacenamiento en cada unidad de compilación.

Al omitir estático, el enlace es externo de forma predeterminada. Una vez más, la const ness lo ha salvado : el compilador puede optimizar el uso en línea. Si elimina la constante , obtendrá un error de símbolos definidos múltiples en el momento del enlace.

Seb Rose
fuente
Creo que el compilador debe asignar espacio para una const int en todos los casos, ya que otro módulo siempre podría decir "extern const int cualquiera; algo (y lo que sea);"
1

No puede declarar una variable estática sin definirla también (esto se debe a que los modificadores de clase de almacenamiento static y extern son mutuamente excluyentes). Se puede definir una variable estática en un archivo de encabezado, pero esto haría que cada archivo de origen que incluye el archivo de encabezado tenga su propia copia privada de la variable, que probablemente no sea lo que se pretendía.

Gajendra Kumar
fuente
"... pero esto haría que cada archivo de origen que incluye el archivo de encabezado tenga su propia copia privada de la variable, que probablemente no sea lo que se pretendía". - Debido al fiasco de la orden de inicialización estática , es posible que se requiera tener una copia en cada unidad de traducción.
jww
1

Las variables const son estáticas por defecto en C ++, pero extern C. Entonces, si usa C ++, no tiene sentido qué construcción usar.

(7.11.6 C ++ 2003 y Apexndix C tiene ejemplos)

Ejemplo en comparar fuentes de compilación / enlace como programa C y C ++:

bruziuz:~/test$ cat a.c
const int b = 22;
int main(){return 0;}
bruziuz:~/test$ cat b.c
const int b=2;
bruziuz:~/test$ gcc -x c -std=c89 a.c b.c
/tmp/ccSKKIRZ.o:(.rodata+0x0): multiple definition of `b'
/tmp/ccDSd0V3.o:(.rodata+0x0): first defined here
collect2: error: ld returned 1 exit status
bruziuz:~/test$ gcc -x c++ -std=c++03 a.c b.c 
bruziuz:~/test$ 
bruziuz:~/test$ gcc --version | head -n1
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609
bruziuz
fuente
No es sentido en aún incluyendo el static. Señala la intención / conciencia de lo que está haciendo el programador y mantiene la paridad con otros tipos de declaración (y, fwiw, C) que carecen de lo implícito static. Es como incluir virtualy últimamente overrideen declaraciones de funciones primordiales: no es necesario, pero es mucho más autodocumentado y, en el caso de este último, propicio para el análisis estático.
underscore_d
Estoy absolutamente de acuerdo. Por ejemplo, en cuanto a mí en la vida real, siempre lo escribo explícitamente.
bruziuz
"Entonces, si usa C ++, no tiene sentido qué construcción usar ..." - Hmm ... Acabo de compilar un proyecto que se usa constsolo en una variable en un encabezado con g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2). Resultó en aproximadamente 150 símbolos definidos multiplicados (uno para cada unidad de traducción se incluyó el encabezado). Creo que necesitamos, ya sea static, inlineo un / espacio de nombres sin nombre en el anonimato para evitar la vinculación externa.
jww
Intenté baby-example con gcc-5.4 con declarar const intdentro del alcance del espacio de nombres y en el espacio de nombres global. Y está compilado y sigue la regla "Los objetos declarados const y no explícitamente declarados extern tienen enlace interno". ".... Tal vez en el proyecto, por alguna razón, este encabezado se incluye en las fuentes compiladas de C, donde las reglas son completamente diferentes.
bruziuz
@jww Subí un ejemplo con un problema de vinculación para C y sin problemas para C ++
bruziuz
0

Static evita que otra unidad de compilación extermine esa variable para que el compilador pueda simplemente "alinear" el valor de la variable donde se usa y no crear almacenamiento de memoria para ella.

En su segundo ejemplo, el compilador no puede asumir que algún otro archivo fuente no lo externará, por lo que en realidad debe almacenar ese valor en la memoria en algún lugar.

Jim Buck
fuente
-2

Static evita que el compilador agregue varias instancias. Esto se vuelve menos importante con la protección #ifndef, pero asumiendo que el encabezado está incluido en dos bibliotecas separadas y la aplicación está vinculada, se incluirían dos instancias.

Superpolock
fuente
suponiendo que por "bibliotecas" te refieres a unidades de traducción , entonces no, los guardias de inclusión no hacen absolutamente nada para evitar múltiples definiciones, ya que solo protegen contra inclusiones repetidas dentro de la misma unidad de traducción . por tanto, no hacen nada en absoluto para hacer static"menos importante". e incluso con ambos, puede terminar con múltiples definiciones vinculadas internamente, lo que probablemente no sea el previsto.
underscore_d