Definición de constante global en C ++

81

Quiero definir una constante en C ++ para que sea visible en varios archivos fuente. Puedo imaginar las siguientes formas de definirlo en un archivo de encabezado:

  1. #define GLOBAL_CONST_VAR 0xFF
  2. int GLOBAL_CONST_VAR = 0xFF;
  3. Alguna función recupera el valor (p int get_GLOBAL_CONST_VAR(). Ej. )
  4. enum { GLOBAL_CONST_VAR = 0xFF; }
  5. const int GLOBAL_CONST_VAR = 0xFF;
  6. extern const int GLOBAL_CONST_VAR; y en un archivo fuente const int GLOBAL_CONST_VAR = 0xFF;

Opción (1): definitivamente no es la opción que le gustaría usar

Opción (2): definir la instancia de la variable en cada archivo de objeto utilizando el archivo de encabezado

Opción (3): la OMI ha terminado con la matanza en la mayoría de los casos

Opción (4): en muchos casos, tal vez no sea bueno ya que enum no tiene un tipo concreto (C ++ 0X agregará la posibilidad de definir el tipo)

Entonces, en la mayoría de los casos, necesito elegir entre (5) y (6). Mis preguntas:

  1. ¿Qué prefieres (5) o (6)?
  2. ¿Por qué (5) está bien y (2) no?
dimba
fuente
1
5 versus 2: "const" implica un vínculo interno. Cuando incluya este encabezado de la versión 5 en varias unidades de traducción, no estará violando la "regla de una definición". Además, const permite al compilador hacer un "plegado constante" mientras que el valor de la variable no constante puede cambiar. La opción 6 es incorrecta. Necesita "extern" en el archivo cpp también para forzar la vinculación externa, de lo contrario obtendrá errores del vinculador. La opción 6 tiene la ventaja de ocultar el valor. Pero también hace imposible el plegado constante.
sellibitze

Respuestas:

32

(5) dice exactamente lo que quiere decir. Además, permite que el compilador lo optimice la mayor parte del tiempo. (6) por otro lado no permitirá que el compilador lo optimice porque el compilador no sabe si lo cambiará eventualmente o no.

Blindy
fuente
1
OTOH, 5 es técnicamente ilegal como violación de la ODR. Sin embargo, la mayoría de los compiladores lo ignorarán.
Joel
Eh, prefiero pensar que no ha definido nada en absoluto, solo le dije al compilador que le diera un nombre bonito a un número. Para todos los efectos, eso es lo que es (5), lo que significa que no hay gastos generales en tiempo de ejecución.
Blindy
2
¿Es (5) una violación de la ODR? Si es así, entonces (6) es preferible. ¿Por qué el compilador "no sabe si lo cambiará" en el caso de (6)? extern const int ...y const int ...ambos son constantes, ¿no es así?
D.Shawley
4
AFAIK, entre 5) y 6), solo 6) está permitido cuando el tipo de constante no está basado en int.
Klaim
11
No hay violación de ODR, los objetos constantes son estáticos por defecto.
Avakar
71

Definitivamente opte por la opción 5: es de tipo seguro y permite que el compilador optimice (no tome la dirección de esa variable :) Además, si está en un encabezado, péguelo en un espacio de nombres para evitar contaminar el alcance global:

// header.hpp
namespace constants
{
    const int GLOBAL_CONST_VAR = 0xFF;
    // ... other related constants

} // namespace constants

// source.cpp - use it
#include <header.hpp>
int value = constants::GLOBAL_CONST_VAR;
Nikolai Fetissov
fuente
3
Recibo el error de redefinición cuando intento incluirlo header.hppen varios archivos fuente.
LRDPRDX
No estoy seguro de por qué esto todavía se vota a favor: han pasado casi diez años, pero hoy en día tenemos constexprenumeraciones mecanografiadas para cosas así.
Nikolai Fetissov
23

(5) es "mejor" que (6) porque se define GLOBAL_CONST_VARcomo una expresión constante integral (ICE) en todas las unidades de traducción. Por ejemplo, podrá usarlo como tamaño de matriz y como etiqueta de caso en todas las unidades de traducción. En el caso de (6) GLOBAL_CONST_VARhabrá un ICE solo en esa unidad de traducción donde está definido y solo después del punto de definición. En otras unidades de traducción, no funcionará como ICE.

Sin embargo, tenga en cuenta que (5) proporciona GLOBAL_CONST_VARun enlace interno, lo que significa que la "identidad de dirección" de GLOBAL_CONST_VARserá diferente en cada unidad de traducción, es decir, &GLOBAL_CONST_VARle dará un valor de puntero diferente en cada unidad de traducción. En la mayoría de los casos de uso, esto no importa, pero si necesita un objeto constante que tenga una "identidad de dirección" global consistente, entonces tendrá que ir con (6), sacrificando la ICE-ness de la constante en el proceso.

Además, cuando el ICE-ness de la constante no es un problema (no es un tipo integral) y el tamaño del tipo crece (no es un tipo escalar), entonces (6) generalmente se convierte en un enfoque mejor que (5).

(2) no está bien porque GLOBAL_CONST_VARen (2) tiene un enlace externo de forma predeterminada. Si lo coloca en el archivo de encabezado, generalmente terminará con múltiples definiciones de GLOBAL_CONST_VAR, lo cual es un error. constlos objetos en C ++ tienen un enlace interno de forma predeterminada, por lo que (5) funciona (y por eso, como dije anteriormente, obtienes una GLOBAL_CONST_VARunidad de traducción separada e independiente ).


A partir de C ++ 17, tiene la opción de declarar

inline extern const int GLOBAL_CONST_VAR = 0xFF;

en un archivo de encabezado. Esto le da un ICE en todas las unidades de traducción (como en el método (5)) al mismo tiempo que mantiene la identidad de dirección global de GLOBAL_CONST_VAR- en todas las unidades de traducción tendrá la misma dirección.

Hormiga
fuente
8

Si usa C ++ 11 o posterior, intente usar constantes en tiempo de compilación:

constexpr int GLOBAL_CONST_VAR{ 0xff };
xninja
fuente
1
En mi humilde opinión, esta es la única solución satisfactoria a este problema.
Lanoxx
5

Si va a ser una constante, entonces debes marcarla como una constante; por eso, en mi opinión, 2 es malo.

El compilador puede usar la naturaleza constante del valor para expandir algunas de las matemáticas y, de hecho, otras operaciones que usan el valor.

La elección entre 5 y 6 - hmm; 5 simplemente se siente mejor para mí.

En 6) el valor se separa innecesariamente de su declaración.

Por lo general, tendría uno o más de estos encabezados que solo definen constantes, etc.dentro de ellos, y luego ninguna otra cosa 'inteligente': bonitos encabezados livianos que se pueden incluir fácilmente en cualquier lugar.

Andras Zoltan
fuente
3
(6) no es un desapego innecesario, es una elección intencional. Si tiene muchas constantes que son grandes, desperdicia mucho espacio en el ejecutable si no las declara como en (6). Puede suceder en bibliotecas matemáticas ... el desperdicio puede ser inferior a 100k, pero incluso eso es importante a veces. (Algunos compiladores tienen otras formas de solucionar esto, creo que MSVC tiene un atributo "una vez", o algo similar.)
Dan Olson
Con (5) no puede estar realmente seguro de que seguirá siendo constante (siempre puede deshacerse de la constness). Es por eso que todavía prefiero el tipo enum.
fmuecke
@Dan Olson - ese es un muy buen punto - Mi respuesta se basó en el hecho de que el tipo involucrado aquí es un int; pero cuando se trata de valores más grandes, la declaración externa es un plan mejor.
Andras Zoltan
@fmuecke: sí, tiene razón, en cuyo caso el valor de enumeración evita esto. ¿Pero eso significa que siempre debemos proteger nuestros valores de las escrituras de esta manera? Si un programador quiere abusar del código, hay tantas áreas en las que un (target_type *) ((void *) & value) puede causar estragos, que no podemos atrapar, que a veces solo tenemos que confiar en ellos; y de hecho nosotros mismos, ¿no?
Andras Zoltan
@fmuecke Una variable que se declara constante no puede ser cambiada por el programa (intentar hacerlo es un comportamiento indefinido). const_cast solo se define en situaciones en las que la variable original no se declaró const (por ejemplo, pasando un valor no constante a una función como const &).
David Stone
5

Para responder a su segunda pregunta:

(2) es ilegal porque viola la regla de una definición. Define GLOBAL_CONST_VARen cada archivo dónde se incluye, es decir, más de una vez. (5) es legal porque no está sujeto a la regla de una definición. Cada uno GLOBAL_CONST_VARes una definición separada, local para ese archivo donde está incluido. Todas esas definiciones comparten el mismo nombre y valor, por supuesto, pero sus direcciones podrían diferir.

MSalters
fuente
4

C ++ 17 inlinevariables

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

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.

Probado en GCC 7.4.0, Ubuntu 18.04.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
2
const int GLOBAL_CONST_VAR = 0xFF;

porque es una constante!


fuente
1
Y no se tratará como una macro, lo que facilitará la depuración.
kayleeFrye_onDeck
-1, esto dará lugar a advertencias / errores de redefinición al incluir el encabezado en varios archivos de origen. Además, esta respuesta es un duplicado de la respuesta de Nikolai Fetissov.
lanoxx
1

Depende de sus requisitos. (5) es el mejor para el uso más normal, pero a menudo resulta en la ocupación constante de espacio de almacenamiento en cada archivo de objeto. (6) puede evitar esto en situaciones en las que es importante.

(4) también es una opción decente si su prioridad es garantizar que nunca se asigne espacio de almacenamiento, pero, por supuesto, solo funciona para constantes integrales.

Dan Olson
fuente
1
#define GLOBAL_CONST_VAR 0xFF // this is C code not C++
int GLOBAL_CONST_VAR = 0xFF; // it is not constant and maybe not compilled
Some function returing the value (e.g. int get_LOBAL_CONST_VAR()) // maybe but exists better desision
enum { LOBAL_CONST_VAR = 0xFF; } // not needed, endeed, for only one constant (enum elms is a simple int, but with secial enumeration)
const int GLOBAL_CONST_VAR = 0xFF; // it is the best
extern const int GLOBAL_CONST_VAR; //some compiller doesn't understand this
den bardadym
fuente