Globale Konstante in C ++ definieren

81

Ich möchte eine Konstante in C ++ definieren, die in mehreren Quelldateien sichtbar ist. Ich kann mir folgende Möglichkeiten vorstellen, um es in einer Header-Datei zu definieren:

  1. #define GLOBAL_CONST_VAR 0xFF
  2. int GLOBAL_CONST_VAR = 0xFF;
  3. Einige Funktion returing den Wert (zB int get_GLOBAL_CONST_VAR())
  4. enum { GLOBAL_CONST_VAR = 0xFF; }
  5. const int GLOBAL_CONST_VAR = 0xFF;
  6. extern const int GLOBAL_CONST_VAR; und in einer Quelldatei const int GLOBAL_CONST_VAR = 0xFF;

Option (1) - ist definitiv nicht die Option, die Sie verwenden möchten

Option (2) - Definieren der Instanz der Variablen in jeder Objektdatei mithilfe der Header-Datei

Option (3) - IMO ist in den meisten Fällen über das Töten hinaus

Option (4) - in vielen Fällen möglicherweise nicht gut, da enum keinen konkreten Typ hat (C ++ 0X fügt die Möglichkeit hinzu, den Typ zu definieren)

In den meisten Fällen muss ich also zwischen (5) und (6) wählen. Meine Fragen:

  1. Was bevorzugen Sie (5) oder (6)?
  2. Warum ist (5) in Ordnung, (2) nicht?
dimba
quelle
1
5 gegen 2: "const" impliziert eine interne Verknüpfung. Wenn Sie diesen Header der Version 5 in mehrere Übersetzungseinheiten aufnehmen, verstoßen Sie nicht gegen die "One Definition Rule". Außerdem erlaubt const dem Compiler, "konstantes Falten" durchzuführen, während sich der Wert der Nicht-const-Variablen ändern kann. Option 6 ist falsch. Sie benötigen auch "extern" in der CPP-Datei, um eine externe Verknüpfung zu erzwingen. Andernfalls werden Linkerfehler angezeigt. Option 6 hat den Vorteil, dass der Wert ausgeblendet wird. Es macht aber auch ein ständiges Falten unmöglich.
Sellibitze

Antworten:

32

(5) sagt genau das, was Sie sagen möchten. Außerdem kann der Compiler es die meiste Zeit optimieren. (6) Auf der anderen Seite lässt der Compiler es niemals wegoptimieren, da der Compiler nicht weiß, ob Sie es irgendwann ändern werden oder nicht.

Blind
quelle
1
OTOH, 5 ist als Verstoß gegen die ODR technisch illegal. Die meisten Compiler werden dies jedoch ignorieren.
Joel
Eh, ich denke lieber, dass es überhaupt nichts definiert hat. Ich habe dem Compiler nur gesagt, dass er einer Zahl einen hübschen Namen geben soll. In jeder Hinsicht ist dies (5), was bedeutet, dass zur Laufzeit kein Overhead anfällt.
Blindy
2
Ist (5) ein Verstoß gegen die ODR? Wenn ja, ist (6) vorzuziehen. Warum weiß der Compiler im Fall von (6) nicht, ob Sie es ändern werden ? extern const int ...und const int ...sind beide konstant, nicht wahr?
D. Shawley
4
AFAIK, zwischen 5) und 6), nur 6) ist zulässig, wenn der Typ der Konstante nicht int-basiert ist.
Klaim
11
Es liegt keine ODR-Verletzung vor. Konstante Objekte sind standardmäßig statisch.
Avakar
71

Entscheiden Sie sich auf jeden Fall für Option 5 - sie ist typsicher und ermöglicht dem Compiler die Optimierung (nehmen Sie die Adresse dieser Variablen nicht an :) Auch wenn sie sich in einem Header befindet - stecken Sie sie in einen Namespace, um eine Verschmutzung des globalen Bereichs zu vermeiden:

// 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
quelle
3
Ich erhalte den Neudefinitionsfehler, wenn ich versuche, ihn header.hppin mehrere Quelldateien aufzunehmen.
LRDPRDX
Ich bin mir nicht sicher, warum dies immer noch positiv bewertet wird - es ist fast zehn Jahre her, aber heutzutage haben wir constexprAufzählungen für solche Dinge geschrieben.
Nikolai Fetissov
23

(5) ist "besser" als (6), da es GLOBAL_CONST_VARin allen Übersetzungseinheiten als integraler konstanter Ausdruck (ICE) definiert ist . Sie können es beispielsweise als Arraygröße und als Fallbezeichnung in allen Übersetzungseinheiten verwenden. Im Fall von (6) GLOBAL_CONST_VARwird ein ICE nur in der Übersetzungseinheit sein, in der er definiert ist, und erst nach dem Definitionspunkt. In anderen Übersetzungseinheiten funktioniert es nicht als ICE.

Beachten Sie jedoch, dass (5) eine GLOBAL_CONST_VARinterne Verknüpfung ergibt , was bedeutet, dass die "Adressidentität" von GLOBAL_CONST_VARin jeder Übersetzungseinheit unterschiedlich ist, dh &GLOBAL_CONST_VAR, dass Sie in jeder Übersetzungseinheit einen anderen Zeigerwert erhalten. In den meisten Anwendungsfällen spielt dies keine Rolle. Wenn Sie jedoch ein konstantes Objekt mit einer konsistenten globalen "Adressidentität" benötigen, müssen Sie mit (6) fortfahren und die ICE-Ness der Konstanten in der Prozess.

Wenn die ICE-Ness der Konstante kein Problem darstellt (kein integraler Typ) und die Größe des Typs größer wird (kein skalarer Typ), wird (6) normalerweise zu einem besseren Ansatz als (5).

(2) ist nicht in Ordnung, da GLOBAL_CONST_VARin (2) standardmäßig eine externe Verknüpfung besteht. Wenn Sie es in eine Header-Datei einfügen, erhalten Sie normalerweise mehrere Definitionen von GLOBAL_CONST_VAR, was ein Fehler ist. constObjekte in C ++ haben standardmäßig eine interne Verknüpfung, weshalb (5) funktioniert (und aus diesem Grund erhalten Sie, wie oben erwähnt, GLOBAL_CONST_VARin jeder Übersetzungseinheit eine separate, unabhängige Verknüpfung ).


Ab C ++ 17 haben Sie die Möglichkeit zu deklarieren

inline extern const int GLOBAL_CONST_VAR = 0xFF;

in einer Header-Datei. Dies gibt Ihnen einen ICE in allen Übersetzungseinheiten (genau wie Methode (5)) zur gleichen Zeit, wobei die globale Adressidentität von beibehalten wird GLOBAL_CONST_VAR- in allen Übersetzungseinheiten hat er dieselbe Adresse.

Ameise
quelle
8

Wenn Sie C ++ 11 oder höher verwenden, versuchen Sie, Konstanten zur Kompilierungszeit zu verwenden:

constexpr int GLOBAL_CONST_VAR{ 0xff };
xninja
quelle
1
IMHO, dies ist die einzige zufriedenstellende Lösung für dieses Problem.
Lanoxx
5

Wenn es eine Konstante sein soll, sollten Sie sie als Konstante markieren - deshalb ist 2 meiner Meinung nach schlecht.

Der Compiler kann die Konstante des Werts verwenden, um einige der mathematischen und tatsächlich andere Operationen, die den Wert verwenden, zu erweitern.

Die Wahl zwischen 5 und 6 - hmm; 5 fühlt sich einfach besser für mich an.

In 6) wird der Wert unnötigerweise von seiner Deklaration getrennt.

Normalerweise habe ich einen oder mehrere dieser Header, die nur Konstanten usw. definieren, und dann keine anderen "cleveren" Dinge - nette, leichte Header, die überall leicht eingefügt werden können.

Andras Zoltan
quelle
3
(6) ist keine unnötige Ablösung, sondern eine absichtliche Entscheidung. Wenn Sie viele Konstanten haben, die groß sind, verschwenden Sie viel Platz in der ausführbaren Datei, wenn Sie sie nicht wie in (6) deklarieren. Das kann in Mathematikbibliotheken passieren ... der Abfall kann unter 100.000 liegen, aber selbst das ist manchmal wichtig. (Einige Compiler haben andere Möglichkeiten, dies zu umgehen. Ich denke, MSVC hat ein "einmal" -Attribut oder ähnliches.)
Dan Olson
Mit (5) können Sie nicht wirklich sicher sein, dass es const bleibt (Sie könnten die constness immer wegwerfen). Deshalb würde ich immer noch den Aufzählungstyp bevorzugen.
Fmuecke
@ Dan Olson - das ist ein sehr guter Punkt - Meine Antwort basierte auf der Tatsache, dass der Typ, um den es hier geht, ein int ist; Aber wenn es um größere Werte geht, ist die externe Erklärung in der Tat ein besserer Plan.
Andras Zoltan
@fmuecke - Ja, Sie haben Recht - in diesem Fall verhindert der Aufzählungswert dies. Aber heißt das, wir sollten unsere Werte immer auf diese Weise vor Schreibvorgängen schützen? Wenn ein Programmierer Code missbrauchen möchte, gibt es so viele Bereiche, in denen ein (target_type *) ((void *) & value) Cast Chaos anrichten kann, dass wir ihn nicht fangen können, dass wir manchmal einfach unser Vertrauen in sie setzen müssen. und in der Tat wir selbst, nein?
Andras Zoltan
@fmuecke Eine Variable, die als const deklariert ist, kann vom Programm nicht geändert werden (der Versuch, dies zu tun, ist undefiniertes Verhalten). const_cast wird nur in Situationen definiert, in denen die ursprüngliche Variable nicht als const deklariert wurde (z. B. Übergabe eines Nicht-const-Werts an eine Funktion als const &).
David Stone
5

So beantworten Sie Ihre zweite Frage:

(2) ist illegal, weil es gegen die One Definition Rule verstößt. Es definiert GLOBAL_CONST_VARin jeder Datei, in der es enthalten ist, dh mehr als einmal. (5) ist legal, da es nicht der One Definition Rule unterliegt. Jedes GLOBAL_CONST_VARist eine separate Definition, lokal für die Datei, in der es enthalten ist. Alle diese Definitionen haben natürlich den gleichen Namen und Wert, aber ihre Adressen können unterschiedlich sein.

MSalters
quelle
4

C ++ 17 inlineVariablen

Mit dieser fantastischen C ++ 17-Funktion können wir:

  • Verwenden Sie bequemerweise nur eine einzige Speicheradresse für jede Konstante
  • Speichern Sie es als constexpr: Wie deklariere ich constexpr extern?
  • Machen Sie es in einer einzigen Zeile aus einem Header

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;
}

Kompilieren und ausführen:

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 stromaufwärts .

Siehe auch: Wie funktionieren Inline-Variablen?

C ++ - Standard für Inline-Variablen

Der C ++ - Standard garantiert, dass die Adressen gleich sind. C ++ 17 N4659 Standardentwurf 10.1.6 "Der Inline-Spezifizierer":

6 Eine Inline-Funktion oder Variable mit externer Verknüpfung muss in allen Übersetzungseinheiten dieselbe Adresse haben.

cppreference https://en.cppreference.com/w/cpp/language/inline erklärt, dass, wenn staticnicht angegeben, eine externe Verknüpfung besteht.

Inline-Variablenimplementierung

Wir können beobachten, wie es implementiert wird mit:

nm main.o notmain.o

was beinhaltet:

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

und man nmsagt über u:

"u" Das Symbol ist ein eindeutiges globales Symbol. Dies ist eine GNU-Erweiterung des Standardsatzes von ELF-Symbolbindungen. Für ein solches Symbol stellt der dynamische Linker sicher, dass im gesamten Prozess nur ein Symbol mit diesem Namen und Typ verwendet wird.

Wir sehen also, dass es dafür eine dedizierte ELF-Erweiterung gibt.

Getestet auf GCC 7.4.0, Ubuntu 18.04.

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

weil es eine Konstante ist!


quelle
1
Und es wird nicht wie ein Makro behandelt, was das Debuggen erleichtert.
KayleeFrye_onDeck
-1, Dies führt zu Warnungen / Fehlern bei der Neudefinition, wenn der Header in mehrere Quelldateien aufgenommen wird. Auch diese Antwort ist ein Duplikat von Nikolai Fetissovs Antwort.
Lanoxx
1

Das hängt von Ihren Anforderungen ab. (5) ist das Beste für die meisten normalen Verwendungszwecke, führt jedoch häufig dazu, dass in jeder Objektdatei ständig Speicherplatz belegt wird. (6) kann dies in Situationen umgehen, in denen es wichtig ist.

(4) ist auch eine gute Wahl, wenn Ihre Priorität darin besteht, sicherzustellen, dass niemals Speicherplatz zugewiesen wird, sondern natürlich nur für integrale Konstanten.

Dan Olson
quelle
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
quelle