¿Qué significa 'const static' en C y C ++?

117
const static int foo = 42;

Vi esto en un código aquí en StackOverflow y no pude averiguar qué hace. Luego vi algunas respuestas confusas en otros foros. Mi mejor suposición es que se usa en C para ocultar la constante foode otros módulos. ¿Es esto correcto? Si es así, ¿por qué alguien lo usaría en un contexto de C ++ en el que puede hacerlo private?

c0m4
fuente

Respuestas:

113

Tiene usos tanto en C como en C ++.

Como adivinó, la staticparte limita su alcance a esa unidad de compilación . También proporciona inicialización estática. constsimplemente le dice al compilador que no permita que nadie lo modifique. Esta variable se coloca en el segmento de datos o bss, según la arquitectura, y podría estar en la memoria marcada como de solo lectura.

Así es como C trata estas variables (o cómo C ++ trata las variables de espacio de nombres). En C ++, un miembro marcado statices compartido por todas las instancias de una clase determinada. Si es privado o no, no afecta el hecho de que una variable sea compartida por varias instancias. Tener constallí le advertirá si algún código intenta modificarlo.

Si fuera estrictamente privado, cada instancia de la clase obtendría su propia versión (a pesar del optimizador).

Chris Arguin
fuente
1
El ejemplo original habla de una "variable privada". Por lo tanto, este es un miembro y la estática no tiene ningún efecto sobre el enlace. Debe eliminar "la parte estática limita su alcance a ese archivo".
Richard Corden
La "sección especial" se conoce como el segmento de datos, que comparte con todas las demás variables globales, como "cadenas" explícitas y matrices globales. Esto se opone al segmento de código.
spoulson
@Richard: ¿qué te hace pensar que es miembro de una clase? No hay nada en la pregunta que lo diga. Si es un miembro de una clase, entonces tiene razón, pero si es solo una variable declarada en el ámbito global, entonces Chris tiene razón.
Graeme Perrow
1
El cartel original mencionaba lo privado como una posible mejor solución, pero no como el problema original.
Chris Arguin
@Graeme, OK, entonces no es "definitivamente" un miembro; sin embargo, esta respuesta hace declaraciones que solo se aplican a los miembros del espacio de nombres y esas declaraciones son incorrectas para las variables de los miembros. Dada la cantidad de votos, ese error puede confundir a alguien que no esté muy familiarizado con el idioma; debería corregirse.
Richard Corden
212

Mucha gente dio la respuesta básica, pero nadie señaló que en C ++ el valor constpredeterminado es statica namespacenivel (y algunos dieron información incorrecta). Consulte la sección 3.5.3 del estándar C ++ 98.

Primero algunos antecedentes:

Unidad de traducción: un archivo de origen después de que el preprocesador (recursivamente) incluyó todos sus archivos de inclusión.

Enlace estático: un símbolo solo está disponible dentro de su unidad de traducción.

Enlace externo: un símbolo está disponible en otras unidades de traducción.

A namespacenivel

Esto incluye el espacio de nombres global, también conocido como variables globales .

static const int sci = 0; // sci is explicitly static
const int ci = 1;         // ci is implicitly static
extern const int eci = 2; // eci is explicitly extern
extern int ei = 3;        // ei is explicitly extern
int i = 4;                // i is implicitly extern
static int si = 5;        // si is explicitly static

A nivel de función

staticsignifica que el valor se mantiene entre llamadas a funciones.
La semántica de las staticvariables de función es similar a las variables globales en el sentido de que residen en el segmento de datos del programa (y no en la pila o en el montón). Consulte esta pregunta para obtener más detalles sobre staticla vida útil de las variables.

A classnivel

staticsignifica que el valor se comparte entre todas las instancias de la clase y constsignifica que no cambia.

Motti
fuente
2
A nivel de función: la estática no es redundante con const, pueden comportarse de manera diferente const int *foo(int x) {const int b=x;return &b};versusconst int *foo(int x) {static const int b=x;return &b};
Hanczar
1
La pregunta es sobre C y C ++, por lo que debe incluir una nota sobre la constimplicación staticen este último.
Nikolai Ruhe
@Motti: Gran respuesta. ¿Podría explicar qué hace que lo que sea redundante a nivel de función? ¿Estás diciendo que la constdeclaración también implica staticallí? Por ejemplo, si desecha consty modifica el valor, ¿se modificarán todos los valores?
Cookie
1
@Motti constno implica estática a nivel de función, eso sería una pesadilla de concurrencia (¡const! = Expresión constante), todo a nivel de función es implícita auto. Dado que esta pregunta también está etiquetada [c], debo mencionar que un nivel global const intestá implícitamente externen C. Sin embargo, las reglas que tiene aquí describen perfectamente C ++.
Ryan Haining
1
Y en C ++, en los tres casos, staticindica que la variable es de duración estática (solo existe una copia, que dura desde el principio del programa hasta su final), y tiene un enlace interno / estático si no se especifica lo contrario (esto es anulado por la función enlace para variables estáticas locales, o enlace de clase para miembros estáticos). Las principales diferencias están en lo que esto implica en cada situación en la que statices válido.
Justin Time - Reincorpora a Monica
45

Esa línea de código puede aparecer en varios contextos diferentes y, aunque se comporta aproximadamente de la misma manera, existen pequeñas diferencias.

Alcance del espacio de nombres

// foo.h
static const int i = 0;

' i' será visible en cada unidad de traducción que incluya el encabezado. Sin embargo, a menos que realmente use la dirección del objeto (por ejemplo. ' &i'), Estoy bastante seguro de que el compilador tratará ' i' simplemente como un tipo seguro 0. Si dos unidades de traducción más toman el " &i", la dirección será diferente para cada unidad de traducción.

// foo.cc
static const int i = 0;

' i' tiene un enlace interno, por lo que no se puede hacer referencia a él desde fuera de esta unidad de traducción. Sin embargo, nuevamente, a menos que use su dirección, lo más probable es que se trate como un tipo seguro 0.

Una cosa que vale la pena señalar, es que la siguiente declaración:

const int i1 = 0;

es exactamente igual que static const int i = 0. Una variable en un espacio de nombres declarada con consty no declarada explícitamente con externes implícitamente estática. Si piensa en esto, la intención del comité de C ++ era permitir que las constvariables se declaren en los archivos de encabezado sin necesitar siempre la staticpalabra clave para evitar romper el ODR.

Alcance de la clase

class A {
public:
  static const int i = 0;
};

En el ejemplo anterior, el estándar especifica explícitamente que " i" no necesita definirse si no se requiere su dirección. En otras palabras, si solo usa ' i' como un 0 con seguridad de tipos, el compilador no lo definirá. Una diferencia entre las versiones de clase y espacio de nombres es que la dirección de ' i' (si se usa en dos o más unidades de traducción) será la misma para el miembro de la clase. Donde se usa la dirección, debe tener una definición para ella:

// a.h
class A {
public:
  static const int i = 0;
};

// a.cc
#include "a.h"
const int A::i;            // Definition so that we can take the address
Richard Corden
fuente
2
+1 para señalar que estática const es lo mismo que solo const en el ámbito del espacio de nombres.
Plumenator
En realidad, no hay diferencia entre colocar en "foo.h" o en "foo.cc" ya que .h simplemente se incluye al compilar la unidad de traducción.
Mikhail
2
@Mikhail: Tienes razón. Se supone que un encabezado se puede incluir en varias TU y, por lo tanto, fue útil hablar de eso por separado.
Richard Corden
24

Es una optimización de espacio pequeño.

Cuando tu dices

const int foo = 42;

No está definiendo una constante, sino creando una variable de solo lectura. El compilador es lo suficientemente inteligente como para usar 42 cada vez que ve foo, pero también le asignará espacio en el área de datos inicializados. Esto se hace porque, como se define, foo tiene un enlace externo. Otra unidad de compilación puede decir:

extern const int foo;

Para acceder a su valor. Esa no es una buena práctica, ya que esa unidad de compilación no tiene idea del valor de foo. Simplemente sabe que es un int const y tiene que recargar el valor de la memoria cada vez que se usa.

Ahora, al declarar que es estático:

static const int foo = 42;

El compilador puede hacer su optimización habitual, pero también puede decir "oye, nadie fuera de esta unidad de compilación puede ver foo y sé que siempre es 42, por lo que no es necesario asignarle espacio".

También debo señalar que en C ++, la forma preferida de evitar que los nombres escapen de la unidad de compilación actual es usar un espacio de nombres anónimo:

namespace {
    const int foo = 42; // same as static definition above
}
Ferruccio
fuente
1
, u mencionó sin usar static "también le asignará espacio en el área de datos inicializados". y al usar estática "no es necesario asignar ningún espacio para ella". (¿De dónde el compilador está eligiendo el val entonces?) ¿Puede explicar en términos de montón y pila dónde se almacena la variable. Corríjame si la estoy interpretando incorrecto .
Nihar
@ N.Nihar: el área de datos estáticos es una porción de memoria de tamaño fijo que contiene todos los datos que tienen vínculos estáticos. Es "asignado" por el proceso de cargar el programa en la memoria. No es parte de la pila ni del montón.
Ferruccio
¿Qué sucede si tengo una función que devuelve un puntero a foo? ¿Eso rompe la optimización?
nw.
@nw: Sí, tendría que hacerlo.
Ferruccio
8

Falta un 'int'. Debería ser:

const static int foo = 42;

En C y C ++, declara una constante entera con un alcance de archivo local de valor 42.

¿Por qué 42? Si aún no lo sabe (y es difícil creer que no lo sabe), es una referencia a la Respuesta a la vida, el universo y todo .

Kevin
fuente
Gracias ... ahora cada vez ... por el resto de mi vida ... cuando vea 42, siempre pensaré en esto. jaja
Inisheer
Esta es una prueba positiva de que el universo fue creado con 13 dedos (la pregunta y la respuesta coinciden en base 13).
paxdiablo
Son los ratones. 3 dedos en cada pie, más una cola te da la base 13.
KeithB
En realidad, no necesita el 'int' en una declaración, aunque definitivamente es de buen gusto escribirlo. C siempre asume tipos 'int' por defecto. ¡Intentalo!
Ephemient
"con alcance de archivo local de valor 42" ?? ¿O es para toda la unidad de compilación?
aniliitb10
4

En C ++,

static const int foo = 42;

es la forma preferida de definir y usar constantes. Es decir, use esto en lugar de

#define foo 42

porque no subvierte el sistema de seguridad de tipos.

paxos1977
fuente
4

A todas las grandes respuestas, quiero agregar un pequeño detalle:

Si escribe complementos (por ejemplo, DLL o bibliotecas .so para ser cargadas por un sistema CAD), entonces static es un salvavidas que evita colisiones de nombres como esta:

  1. El sistema CAD carga un complemento A, que tiene un "const int foo = 42;" en eso.
  2. El sistema carga un complemento B, que tiene "const int foo = 23;" en eso.
  3. Como resultado, el complemento B usará el valor 42 para foo, porque el cargador de complementos se dará cuenta de que ya existe un "foo" con enlace externo.

Peor aún: el paso 3 puede comportarse de manera diferente según la optimización del compilador, el mecanismo de carga del complemento, etc.

Tuve este problema una vez con dos funciones auxiliares (mismo nombre, comportamiento diferente) en dos complementos. Declararlos estáticos resolvió el problema.

Negro
fuente
Algo parecía extraño acerca de las colisiones de nombres entre los dos complementos, lo que me llevó a examinar el mapa de enlaces de una de mis muchas DLL que define m_hDfltHeap como un identificador con enlace externo. Efectivamente, está ahí para que todo el mundo lo vea y use, listado en el mapa de vínculos como _m_hDfltHeap. Me había olvidado por completo de este factoide.
David A. Gray
4

Según la especificación C99 / GNU99:

  • static

    • es un especificador de clase de almacenamiento

    • los objetos del ámbito de nivel de archivo tienen un vínculo externo de forma predeterminada

    • los objetos de ámbito de nivel de archivo con especificador estático tienen un enlace interno
  • const

    • es un calificador de tipo (es parte del tipo)

    • palabra clave aplicada a la instancia izquierda inmediata, es decir

      • MyObj const * myVar; - puntero no calificado al tipo de objeto calificado constante

      • MyObj * const myVar; - puntero calificado const al tipo de objeto no calificado

    • Uso más a la izquierda: se aplica al tipo de objeto, no a la variable

      • const MyObj * myVar; - puntero no calificado al tipo de objeto calificado constante

ASÍ:

static NSString * const myVar; - puntero constante a cadena inmutable con enlace interno.

La ausencia de la staticpalabra clave hará que el nombre de la variable sea global y podría generar conflictos de nombres dentro de la aplicación.

Alexey Pelekh
fuente
4

C ++ 17 inlinevariables

Si buscó en Google "C ++ const static", entonces es muy probable que lo que realmente quiera usar sean las variables en línea de C ++ 17 .

Esta impresionante característica de C ++ 17 nos permite:

  • use convenientemente una única dirección de memoria para cada constante
  • guárdelo como constexpr: ¿Cómo declarar constexpr extern?
  • hazlo en una sola línea desde un encabezado

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Compila y ejecuta:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub aguas arriba .

Consulte también: ¿Cómo funcionan las variables en línea?

Estándar C ++ en variables en línea

El estándar C ++ garantiza que las direcciones serán las mismas. Borrador estándar 10.1.6 de C ++ 17 N4659 "El especificador en línea":

6 Una función o variable en línea con enlace externo deberá tener la misma dirección en todas las unidades de traducción.

cppreference https://en.cppreference.com/w/cpp/language/inline explica que si staticno se proporciona, entonces tiene un enlace externo.

Implementación de variables en línea de GCC

Podemos observar cómo se implementa con:

nm main.o notmain.o

que contiene:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

y man nmdice sobre u:

"u" El símbolo es un símbolo global único. Esta es una extensión GNU del conjunto estándar de enlaces de símbolos ELF. Para tal símbolo, el enlazador dinámico se asegurará de que en todo el proceso haya solo un símbolo con este nombre y tipo en uso.

por lo que vemos que hay una extensión ELF dedicada para esto.

Pre-C ++ 17: extern const

Antes de C ++ 17, y en C, podemos lograr un efecto muy similar con an extern const, que conducirá a que se utilice una única ubicación de memoria.

Las desventajas inlineson:

  • no es posible hacer la variable constexprcon esta técnica, solo inlinepermite que: ¿Cómo declarar constexpr extern?
  • es menos elegante, ya que debe declarar y definir la variable por separado en el encabezado y el archivo cpp

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHub aguas arriba .

Alternativas de encabezado previo a C ++ 17

Estos no son tan buenos como la externsolución, pero funcionan y solo ocupan una única ubicación de memoria:

Una constexprfunción, porque constexprimplicainline y inline permite (fuerza) que la definición aparezca en cada unidad de traducción :

constexpr int shared_inline_constexpr() { return 42; }

y apuesto a que cualquier compilador decente incluirá la llamada.

También puede usar una variable estática consto constexprcomo en:

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

pero no puede hacer cosas como tomar su dirección, o de lo contrario se usa odr, vea también: Definición de miembros de datos estáticos constexpr

C

En C, la situación es la misma que en C ++ antes de C ++ 17, he subido un ejemplo en: ¿Qué significa "estático" en C?

La única diferencia es que en C ++, constimplica staticpara globales, pero no en C: C ++ semántica de `static const` vs` const`

¿Alguna forma de alinearlo completamente?

TODO: ¿hay alguna forma de alinear completamente la variable, sin usar memoria en absoluto?

Muy parecido a lo que hace el preprocesador.

Esto requeriría de alguna manera:

  • prohibir o detectar si se toma la dirección de la variable
  • agregue esa información a los archivos de objeto ELF y deje que LTO la optimice

Relacionado:

Probado en Ubuntu 18.10, GCC 8.2.0.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
2

Sí, oculta una variable en un módulo de otros módulos. En C ++, lo uso cuando no quiero / necesito cambiar un archivo .h que desencadenará una reconstrucción innecesaria de otros archivos. Además, pongo la estática primero:

static const int foo = 42;

Además, dependiendo de su uso, el compilador ni siquiera le asignará almacenamiento y simplemente "alineará" el valor donde se usa. Sin la estática, el compilador no puede asumir que no se está usando en otro lugar y no puede estar en línea.

Jim Buck
fuente
2

Esta es una constante global visible / accesible solo en el módulo de compilación (archivo .cpp). Por cierto, el uso de estática para este propósito está en desuso. Mejor use un espacio de nombres anónimo y una enumeración:

namespace
{
  enum
  {
     foo = 42
  };
}
Roskoto
fuente
esto obligaría al compilador a no tratar foo como una constante y, como tal, dificulta la optimización.
Nils Pipenbrinck
Los valores de enumeración son siempre constantes, por lo que no veo cómo esto dificultará las optimizaciones
Roskoto
ah - cierto .. mi error. pensó que había usado una variable int simple.
Nils Pipenbrinck
Roskoto, no tengo claro qué beneficio enumtiene en este contexto. ¿Cuidado para elaborar? Por enumslo general, estos solo se usan para evitar que el compilador asigne espacio para el valor (aunque los compiladores modernos no necesitan este enumtruco para ello) y para evitar la creación de punteros al valor.
Konrad Rudolph
Konrad, ¿qué problema ve exactamente al usar una enumeración en este caso? Las enumeraciones se utilizan cuando necesita entradas constantes, que es exactamente el caso.
Roskoto
1

Hacerlo privado aún significaría que aparece en el encabezado. Tiendo a usar "la forma más débil" que funciona. Vea este artículo clásico de Scott Meyers: http://www.ddj.com/cpp/184401197 (se trata de funciones, pero también se puede aplicar aquí).

yrp
fuente