Was bedeutet "const static" in C und C ++?

117
const static int foo = 42;

Ich habe dies in einem Code hier auf StackOverflow gesehen und konnte nicht herausfinden, was es tut. Dann sah ich einige verwirrte Antworten in anderen Foren. Ich vermute, dass es in C verwendet wird, um die Konstante foovor anderen Modulen zu verbergen . Ist das richtig? Wenn ja, warum sollte jemand es in einem C ++ - Kontext verwenden, in dem Sie es einfach erstellen können private?

c0m4
quelle

Antworten:

113

Es wird sowohl in C als auch in C ++ verwendet.

Wie Sie vermutet haben, beschränkt der staticTeil seinen Umfang auf diese Kompilierungseinheit . Es ermöglicht auch eine statische Initialisierung. constweist den Compiler lediglich an, sich von niemandem ändern zu lassen. Diese Variable wird je nach Architektur entweder in das Daten- oder das BSS-Segment eingefügt und befindet sich möglicherweise im Speicher, der als schreibgeschützt markiert ist.

So behandelt C diese Variablen (oder wie C ++ Namespace-Variablen behandelt). In C ++ wird ein markiertes Mitglied staticvon allen Instanzen einer bestimmten Klasse gemeinsam genutzt. Ob es privat ist oder nicht, hat keinen Einfluss darauf, dass eine Variable von mehreren Instanzen gemeinsam genutzt wird. Wenn Sie constdort sind, werden Sie gewarnt, wenn ein Code versuchen würde, dies zu ändern.

Wenn es streng privat wäre, würde jede Instanz der Klasse eine eigene Version erhalten (trotz Optimierer).

Chris Arguin
quelle
1
Das ursprüngliche Beispiel spricht von einer "privaten Variablen". Daher ist dies ein Mechmer und statische Aufladung hat keinen Einfluss auf die Verknüpfung. Sie sollten entfernen, dass "der statische Teil den Umfang auf diese Datei beschränkt".
Richard Corden
Der "spezielle Abschnitt" ist als Datensegment bekannt, das er mit allen anderen globalen Variablen wie expliziten "Zeichenfolgen" und globalen Arrays teilt. Dies steht im Gegensatz zum Codesegment.
Spoulson
@Richard - warum denkst du, dass es ein Mitglied einer Klasse ist? Es gibt nichts in der Frage, was besagt, dass es so ist. Wenn es ein Mitglied einer Klasse ist, haben Sie Recht, aber wenn es nur eine Variable ist, die im globalen Bereich deklariert ist, dann hat Chris Recht.
Graeme Perrow
1
Das Originalplakat erwähnte privat als eine mögliche bessere Lösung, aber nicht als das ursprüngliche Problem.
Chris Arguin
@Graeme, OK, es ist also nicht "definitiv" ein Mitglied - diese Antwort enthält jedoch Anweisungen, die nur für Namespace-Mitglieder gelten, und diese Anweisungen sind für Mitgliedsvariablen falsch. Angesichts der Anzahl der Stimmen kann dieser Fehler jemanden verwirren, der mit der Sprache nicht sehr vertraut ist - er sollte behoben werden.
Richard Corden
212

Viele Leute gaben die grundlegende Antwort, aber niemand wies darauf hin, dass in C ++ conststandardmäßig staticauf namespaceEbene (und einige gaben falsche Informationen). Siehe den C ++ 98-Standardabschnitt 3.5.3.

Zunächst einige Hintergrundinformationen:

Übersetzungseinheit: Eine Quelldatei nach dem Vorprozessor (rekursiv) enthielt alle Include-Dateien.

Statische Verknüpfung: Ein Symbol ist nur innerhalb seiner Übersetzungseinheit verfügbar.

Externe Verknüpfung: Ein Symbol ist von anderen Übersetzungseinheiten erhältlich.

Auf der namespaceEbene

Dies schließt den globalen Namespace, auch bekannt als globale Variablen, ein .

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

Auf Funktionsebene

staticbedeutet, dass der Wert zwischen Funktionsaufrufen beibehalten wird.
Die Semantik von Funktionsvariablen staticähnelt globalen Variablen, da sie sich im Datensegment des Programms befinden (und nicht im Stapel oder im Heap). Weitere Informationen zur Lebensdauer von Variablen finden Sie in dieser Fragestatic .

Auf der classEbene

staticbedeutet, dass der Wert von allen Instanzen der Klasse gemeinsam genutzt wird und constsich nicht ändert.

Motti
quelle
2
Auf Funktionsebene: statisch nicht mit const reduntant ist, können sie sich anders verhalten im const int *foo(int x) {const int b=x;return &b};Vergleichconst int *foo(int x) {static const int b=x;return &b};
Hanczar
1
Die Frage bezieht sich sowohl auf C als auch auf C ++, daher sollten Sie einen Hinweis consthinzufügen, dass nur staticletzteres impliziert wird .
Nikolai Ruhe
@ Motti: Tolle Antwort. Könnten Sie bitte erklären, was was auf Funktionsebene überflüssig macht? Wollen Sie damit sagen, dass constErklärung auch staticdort impliziert ? Wie in, wenn Sie wegwerfen constund den Wert ändern, werden alle Werte geändert?
Cookie
1
@Motti constimpliziert nicht statisch auf Funktionsebene, das wäre ein Albtraum der Parallelität (const! = Konstanter Ausdruck), alles auf Funktionsebene ist implizit auto. Da diese Frage auch mit [c] markiert ist, sollte ich erwähnen, dass eine globale Ebene const intimplizit externin C enthalten ist. Die Regeln, die Sie hier haben, beschreiben C ++ jedoch perfekt.
Ryan Haining
1
In C ++ wird in allen drei Fällen staticangegeben, dass die Variable eine statische Dauer hat (es ist nur eine Kopie vorhanden, die vom Anfang bis zum Ende des Programms dauert) und eine interne / statische Verknüpfung aufweist, sofern nicht anders angegeben (dies wird von der Funktion überschrieben) Verknüpfung für lokale statische Variablen oder Verknüpfung der Klasse für statische Elemente). Die Hauptunterschiede bestehen darin, was dies in jeder Situation impliziert, in der dies staticgültig ist.
Justin Time - Stellen Sie Monica
45

Diese Codezeile kann tatsächlich in verschiedenen Kontexten vorkommen, und obwohl sie sich ungefähr gleich verhält, gibt es kleine Unterschiede.

Namespace-Bereich

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

' i' wird in jeder Übersetzungseinheit angezeigt, die den Header enthält. Wenn Sie jedoch nicht tatsächlich die Adresse des Objekts verwenden (z. B. ' &i'), bin ich mir ziemlich sicher, dass der Compiler ' i' einfach als typsicher behandelt 0. Wenn zwei weitere Übersetzungseinheiten das ' &i' annehmen , ist die Adresse für jede Übersetzungseinheit unterschiedlich.

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

' i' hat eine interne Verknüpfung und kann daher nicht von außerhalb dieser Übersetzungseinheit referenziert werden. Wenn Sie jedoch nicht die Adresse verwenden, wird diese höchstwahrscheinlich als typsicher behandelt 0.

Hervorzuheben ist die folgende Erklärung:

const int i1 = 0;

ist genau das gleiche wie static const int i = 0. Eine Variable in einem mit constund nicht explizit mit deklarierten Namespace externist implizit statisch. Wenn Sie darüber nachdenken, war es die Absicht des C ++ - Komitees, die constDeklaration von Variablen in Header-Dateien zuzulassen , ohne immer das staticSchlüsselwort zu benötigen , um eine Unterbrechung des ODR zu vermeiden.

Klassenumfang

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

Im obigen Beispiel gibt der Standard explizit an, dass ' i' nicht definiert werden muss, wenn seine Adresse nicht erforderlich ist. Mit anderen Worten, wenn Sie ' i' nur als typsichere 0 verwenden, definiert der Compiler diese nicht. Ein Unterschied zwischen der Klassen- und der Namespace-Version besteht darin, dass die Adresse von ' i' (wenn sie in zwei oder mehr Übersetzungseinheiten verwendet wird) für das Klassenmitglied gleich ist. Wo die Adresse verwendet wird, müssen Sie eine Definition dafür haben:

// 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
quelle
2
+1 für den Hinweis, dass statische const im Namespace-Bereich mit const identisch ist.
Plumenator
Es gibt eigentlich keinen Unterschied zwischen der Platzierung in "foo.h" oder in "foo.cc", da .h beim Kompilieren der Übersetzungseinheit einfach berücksichtigt wird.
Mikhail
2
@ Michail: Du hast recht. Es wird davon ausgegangen, dass ein Header in mehreren TUs enthalten sein kann, und daher war es hilfreich, darüber separat zu sprechen.
Richard Corden
24

Es ist eine kleine Raumoptimierung.

Wenn du sagst

const int foo = 42;

Sie definieren keine Konstante, sondern erstellen eine schreibgeschützte Variable. Der Compiler ist intelligent genug, um 42 zu verwenden, wenn er foo sieht, aber er weist ihm auch Speicherplatz im initialisierten Datenbereich zu. Dies geschieht, weil foo wie definiert eine externe Verknüpfung hat. Eine andere Zusammenstellungseinheit kann sagen:

extern const int foo;

Zugriff auf seinen Wert erhalten. Das ist keine gute Praxis, da diese Kompilierungseinheit keine Ahnung hat, welchen Wert foo hat. Es weiß nur, dass es ein const int ist und muss den Wert bei jeder Verwendung aus dem Speicher neu laden.

Indem Sie nun erklären, dass es statisch ist:

static const int foo = 42;

Der Compiler kann seine übliche Optimierung durchführen, aber er kann auch sagen: "Hey, niemand außerhalb dieser Kompilierungseinheit kann foo sehen, und ich weiß, dass es immer 42 ist, so dass kein Platz dafür zugewiesen werden muss."

Ich sollte auch beachten, dass in C ++ die bevorzugte Methode, um zu verhindern, dass Namen der aktuellen Kompilierungseinheit entkommen, die Verwendung eines anonymen Namespace ist:

namespace {
    const int foo = 42; // same as static definition above
}
Ferruccio
quelle
1
, u erwähnt ohne statische "es wird auch Speicherplatz im initialisierten Datenbereich dafür zuweisen". und indem Sie statisch "keine Notwendigkeit, Speicherplatz dafür zuzuweisen" verwenden (von wo aus der Compiler dann den Wert auswählt?), können Sie anhand von Heap und Stack erklären, wo die Variable gespeichert wird. Korrigieren Sie mich, wenn ich sie interpretiere falsch .
Nihar
@ N.Nihar - Der statische Datenbereich ist ein Speicherblock mit fester Größe, der alle Daten enthält, die eine statische Verknüpfung aufweisen. Es wird durch das Laden des Programms in den Speicher "zugewiesen". Es ist nicht Teil des Stapels oder Haufens.
Ferruccio
Was passiert, wenn eine Funktion einen Zeiger auf foo zurückgibt? Bricht das die Optimierung?
nw.
@nw: Ja, das müsste es.
Ferruccio
8

Es fehlt ein 'int'. Es sollte sein:

const static int foo = 42;

In C und C ++ deklariert es eine Ganzzahlkonstante mit dem lokalen Dateibereich des Werts 42.

Warum 42? Wenn Sie es noch nicht wissen (und es ist schwer zu glauben, dass Sie es nicht wissen), ist es ein Hinweis auf die Antwort auf das Leben, das Universum und alles .

Kevin
quelle
Danke ... jetzt jedes Mal ... für den Rest meines Lebens ... wenn ich 42 sehe, werde ich immer etwas dagegen tun. haha
Inisheer
Dies ist ein Beweis dafür, dass das Universum mit 13 Fingern erschaffen wurde (Frage und Antwort stimmen tatsächlich mit Basis 13 überein).
Paxdiablo
Es sind die Mäuse. 3 Zehen an jedem Fuß plus ein Schwanz geben Ihnen Basis 13.
KeithB
Sie brauchen das 'int' in einer Deklaration eigentlich nicht, obwohl es definitiv gut schmeckt, es aufzuschreiben. C nimmt standardmäßig immer 'int'-Typen an. Versuch es!
Ephemient
"mit lokalem Dateibereich von Wert 42" ?? oder ist es für die gesamte Kompilierungseinheit?
Aniliitb10
4

In C ++

static const int foo = 42;

ist die bevorzugte Methode zum Definieren und Verwenden von Konstanten. Dh dies eher verwenden als

#define foo 42

weil es das Typensicherheitssystem nicht untergräbt.

paxos1977
quelle
4

Zu all den tollen Antworten möchte ich ein kleines Detail hinzufügen:

Wenn Sie Plugins schreiben (z. B. DLLs oder .so-Bibliotheken, die von einem CAD-System geladen werden sollen), ist static ein Lebensretter, der Namenskollisionen wie diese vermeidet:

  1. Das CAD-System lädt ein Plugin A mit einem "const int foo = 42;" drin.
  2. Das System lädt ein Plugin B mit "const int foo = 23;" drin.
  3. Infolgedessen verwendet Plugin B den Wert 42 für foo, da der Plugin-Loader erkennt, dass es bereits ein "foo" mit externer Verknüpfung gibt.

Noch schlimmer: Schritt 3 kann sich je nach Compileroptimierung, Plugin-Lademechanismus usw. unterschiedlich verhalten.

Ich hatte dieses Problem einmal mit zwei Hilfsfunktionen (gleicher Name, unterschiedliches Verhalten) in zwei Plugins. Die statische Erklärung löste das Problem.

Schwarz
quelle
Die Namenskollisionen zwischen den beiden Plug-Ins schienen etwas seltsam zu sein, was mich dazu veranlasste, die Link-Map auf eine meiner vielen DLLs zu untersuchen, die m_hDfltHeap als Handle mit externer Verknüpfung definiert. Sicher genug, dort ist es für die ganze Welt zu sehen und zu verwenden, aufgeführt in der Verknüpfungskarte als _m_hDfltHeap. Ich hatte alles über dieses Faktoid vergessen.
David A. Gray
4

Gemäß C99 / GNU99-Spezifikation:

  • static

    • ist ein Speicherklassenspezifizierer

    • Objekte mit Gültigkeitsbereich auf Dateiebene verfügen standardmäßig über eine externe Verknüpfung

    • Objekte im Bereich der Dateiebene mit statischem Bezeichner sind intern verknüpft
  • const

    • ist ein Typqualifizierer (ist ein Teil des Typs)

    • Schlüsselwort, das auf die unmittelbare linke Instanz angewendet wird - dh

      • MyObj const * myVar; - unqualifizierter Zeiger auf den qualifizierten Objekttyp const

      • MyObj * const myVar; - const qualifizierter Zeiger auf nicht qualifizierten Objekttyp

    • Verwendung ganz links - wird auf den Objekttyp angewendet, nicht auf die Variable

      • const MyObj * myVar; - unqualifizierter Zeiger auf den qualifizierten Objekttyp const

SO:

static NSString * const myVar; - konstanter Zeiger auf unveränderlichen String mit interner Verknüpfung.

Das Fehlen des staticSchlüsselworts macht den Variablennamen global und kann zu Namenskonflikten innerhalb der Anwendung führen.

Alexey Pelekh
quelle
4

C ++ 17 inlineVariablen

Wenn Sie "C ++ const static" gegoogelt haben, ist es sehr wahrscheinlich, dass Sie wirklich C ++ 17-Inline-Variablen verwenden möchten .

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.

Implementierung von GCC-Inline-Variablen

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.

Pre-C ++ 17: extern const

Vor C ++ 17 und in C können wir mit a einen sehr ähnlichen Effekt erzielen extern const, der dazu führt, dass ein einzelner Speicherort verwendet wird.

Die Nachteile inlinesind:

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

Nur Header-Alternativen vor C ++ 17

Diese sind nicht so gut wie die externLösung, aber sie funktionieren und belegen nur einen einzigen Speicherort:

Eine constexprFunktion, weil constexprimpliziertinline und inline erlaubt (erzwingt), dass die Definition auf jeder Übersetzungseinheit erscheint :

constexpr int shared_inline_constexpr() { return 42; }

und ich wette, dass jeder anständige Compiler den Aufruf einbindet.

Sie können auch eine constoder eine constexprstatische Variable wie folgt verwenden:

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

Sie können jedoch nicht die Adresse übernehmen oder sie wird odr-verwendet, siehe auch: Definieren von statischen constexpr-Datenelementen

C.

In C ist die Situation dieselbe wie in C ++ vor C ++ 17, ich habe ein Beispiel hochgeladen unter: Was bedeutet "statisch" in C?

Der einzige Unterschied besteht darin, dass in C ++ für Globale constimpliziert staticwird, in C: C ++ jedoch nicht die Semantik von `static const` vs` const`

Gibt es eine Möglichkeit, es vollständig zu integrieren?

TODO: Gibt es eine Möglichkeit, die Variable vollständig zu inline, ohne überhaupt Speicher zu verwenden?

Ähnlich wie der Präprozessor.

Dies würde irgendwie erfordern:

  • Verbieten oder Erkennen, ob die Adresse der Variablen verwendet wird
  • Fügen Sie diese Informationen zu den ELF-Objektdateien hinzu und lassen Sie sie von LTO optimieren

Verbunden:

Getestet in Ubuntu 18.10, GCC 8.2.0.

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

Ja, eine Variable in einem Modul wird vor anderen Modulen ausgeblendet. In C ++ verwende ich es, wenn ich keine .h-Datei ändern möchte / muss, die eine unnötige Neuerstellung anderer Dateien auslöst. Außerdem habe ich die Statik an die erste Stelle gesetzt:

static const int foo = 42;

Abhängig von seiner Verwendung weist der Compiler ihm nicht einmal Speicher zu und "inline" einfach den Wert, in dem er verwendet wird. Ohne die Statik kann der Compiler nicht davon ausgehen, dass er nicht anderweitig verwendet wird, und kann nicht inline.

Jim Buck
quelle
2

Diese globale Konstante ist nur im Kompilierungsmodul (CPP-Datei) sichtbar / zugänglich. Übrigens ist die Verwendung von Static für diesen Zweck veraltet. Verwenden Sie besser einen anonymen Namespace und eine Aufzählung:

namespace
{
  enum
  {
     foo = 42
  };
}
Roskoto
quelle
Dies würde den Compiler zwingen, foo nicht als Konstante zu behandeln und als solche die Optimierung behindern.
Nils Pipenbrinck
Aufzählungswerte sind immer konstant, daher sehe ich nicht, wie dies Optimierungen behindert
Roskoto
ah - wahr .. mein Fehler. Ich dachte, Sie haben eine einfache int-Variable verwendet.
Nils Pipenbrinck
Roskoto, mir ist nicht klar, welchen Nutzen das enumin diesem Zusammenhang hat. Möchtest du das näher erläutern? Diese enumswerden normalerweise nur verwendet, um zu verhindern, dass der Compiler Speicherplatz für den Wert reserviert (obwohl moderne Compiler diesen enumHack dafür nicht benötigen ) und um die Erstellung von Zeigern auf den Wert zu verhindern.
Konrad Rudolph
Konrad, was genau sehen Sie in diesem Fall bei der Verwendung einer Aufzählung? Aufzählungen werden verwendet, wenn Sie konstante Ints benötigen, was genau der Fall ist.
Roskoto
1

Wenn Sie es privat machen, wird es immer noch in der Kopfzeile angezeigt. Ich neige dazu, "die schwächste" Art zu verwenden, die funktioniert. Siehe diesen klassischen Artikel von Scott Meyers: http://www.ddj.com/cpp/184401197 (es geht um Funktionen, kann aber auch hier angewendet werden).

yrp
quelle