Variablendeklarationen in Header-Dateien - statisch oder nicht?

91

Beim Umgestalten einiger habe #definesich in einer C ++ - Header-Datei ähnliche Deklarationen festgestellt:

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

Die Frage ist, welchen Unterschied wird die statische Aufladung machen, wenn überhaupt? Beachten Sie, dass das mehrfache Einfügen der Header aufgrund des klassischen #ifndef HEADER #define HEADER #endifTricks nicht möglich ist (falls dies wichtig ist).

Bedeutet statisch, dass nur eine Kopie VALerstellt wird, falls der Header in mehr als einer Quelldatei enthalten ist?

rauben
quelle
Verwandte: stackoverflow.com/questions/177437/…
Ciro Santilli 法轮功 冠状 病. 六四 法轮功

Antworten:

107

Die staticbedeutet , dass es eine Kopie sein VALfür jede Quelldatei erstellt es in enthalten ist. Es bedeutet aber auch , dass mehrere Einschlüsse in mehreren Definitionen von nicht dazu führen werden , VALdass wird zur Verknüpfungszeit kollidieren. In C staticmüssten Sie ohne das sicherstellen, dass nur eine Quelldatei definiert ist, VALwährend die anderen Quelldateien dies deklarieren extern. Normalerweise würde man dies tun, indem man es (möglicherweise mit einem Initialisierer) in einer Quelldatei definiert und die externDeklaration in eine Header-Datei einfügt.

static Variablen auf globaler Ebene sind nur in ihrer eigenen Quelldatei sichtbar, unabhängig davon, ob sie über ein Include dort angekommen sind oder sich in der Hauptdatei befanden.


Anmerkung des Herausgebers: In C ++ sind constObjekte, deren Deklaration weder das Schlüsselwort staticnoch enthält, externimplizit static.

Justsalt
quelle
Ich bin ein Fan des letzten Satzes, unglaublich hilfreich. Ich habe die Antwort nicht gewählt, weil 42 besser ist. edit: grammar
RealDeal_EE'18
"Die statische Aufladung bedeutet, dass für jede Quelldatei, in der sie enthalten ist, eine Kopie von VAL erstellt wird." Dies scheint zu implizieren, dass es zwei Kopien von VAL geben würde, wenn zwei Quelldateien die Header-Datei enthalten würden. Ich hoffe, dass dies nicht der Fall ist und dass es immer eine einzelne Instanz von VAL gibt, unabhängig davon, wie viele Dateien den Header enthalten.
Brent212
4
@ Brent212 Der Compiler weiß nicht, ob eine Deklaration / Definition aus einer Header- oder Hauptdatei stammt. Sie hoffen also vergebens. Es wird zwei Kopien von VAL geben, wenn jemand dumm war und eine statische Definition in eine Header-Datei eingefügt hat und diese in zwei Quellen aufgenommen wurde.
Justsalt
1
const Werte haben interne Verknüpfung in C ++
adrianN
112

Die Tags staticund externfür Variablen mit Dateibereich bestimmen, ob auf sie in anderen Übersetzungseinheiten (dh anderen .coder .cppDateien) zugegriffen werden kann .

  • staticgibt die variable interne Verknüpfung an und verbirgt sie vor anderen Übersetzungseinheiten. Variablen mit interner Verknüpfung können jedoch in mehreren Übersetzungseinheiten definiert werden.

  • externgibt die Variable externe Verknüpfung an und macht sie für andere Übersetzungseinheiten sichtbar. In der Regel bedeutet dies, dass die Variable nur in einer Übersetzungseinheit definiert werden darf.

Die Standardeinstellung (wenn Sie nicht angeben staticoder extern) ist einer der Bereiche, in denen sich C und C ++ unterscheiden.

  • In C sind Variablen mit Dateibereich standardmäßig extern(externe Verknüpfung). Wenn Sie C verwenden, VAList staticund ANOTHER_VAList extern.

  • In C ++ sind Variablen mit Dateibereich standardmäßig static(interne Verknüpfung), wenn dies der Fall ist const, und externstandardmäßig, wenn dies nicht der Fall ist . Wenn Sie mit C ++, beide VALund ANOTHER_VALsind static.

Aus einem Entwurf der C-Spezifikation :

6.2.2 Verknüpfungen von Bezeichnern ... -5- Wenn die Deklaration eines Bezeichners für eine Funktion keinen Speicherklassenspezifizierer hat, wird ihre Verknüpfung genau so bestimmt, als ob sie mit dem Speicherklassenspezifizierer extern deklariert worden wäre. Wenn die Deklaration eines Bezeichners für ein Objekt einen Dateibereich und keinen Speicherklassenspezifizierer hat, ist die Verknüpfung extern.

Aus einem Entwurf der C ++ - Spezifikation :

7.1.1 - Speicherklassenspezifizierer [dcl.stc] ... -6- Ein in einem Namespace-Bereich ohne Speicherklassenspezifizierer deklarierter Name verfügt über eine externe Verknüpfung, es sei denn, er verfügt aufgrund einer vorherigen Deklaration über eine interne Verknüpfung, sofern dies nicht der Fall ist deklariert const. Objekte, die als const deklariert und nicht explizit als extern deklariert wurden, haben eine interne Verknüpfung.

bk1e
quelle
47

Die statische Aufladung bedeutet, dass Sie eine Kopie pro Datei erhalten, aber im Gegensatz zu anderen ist dies völlig legal. Sie können dies einfach mit einem kleinen Codebeispiel testen:

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

Wenn Sie dies ausführen, erhalten Sie folgende Ausgabe:

0x446020
0x446040

Limette in Scheiben geschnitten
quelle
5
Danke für das Beispiel!
Kyrol
Ich frage mich , ob TESTwaren const, wenn LTO es in eine einzige Speicherstelle optimieren würde. Aber -O3 -fltovon GCC 8.1 nicht.
Ciro Santilli 21 冠状 病 六四 事件 21
Es wäre illegal, dies zu tun - selbst wenn es konstant ist, garantiert statisch, dass jede Instanz lokal für die Kompilierungseinheit ist. Es könnte wahrscheinlich den Konstantenwert selbst inline setzen, wenn es als Konstante verwendet wird, aber da wir seine Adresse nehmen, muss es einen eindeutigen Zeiger zurückgeben.
Slicedlime
6

constVariablen in C ++ haben eine interne Verknüpfung. Die Verwendung statichat also keine Auswirkung.

Ah

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

two.cpp

#include "a.h"

func1()
{
   cout << i;
}

Wenn dies ein C-Programm wäre, würden Sie i(aufgrund einer externen Verknüpfung) den Fehler "Mehrfachdefinition" erhalten .

Nitin
quelle
2
Nun, das Verwenden statichat den Effekt, dass es die Absicht und das Bewusstsein dessen, was man codiert, sauber signalisiert, was niemals eine schlechte Sache ist. Für mich ist das wie virtualbeim Überschreiben: Wir müssen nicht, aber die Dinge sehen viel intuitiver aus - und stimmen mit anderen Erklärungen überein -, wenn wir dies tun.
underscore_d
Sie könnten mehrere Definitionsfehler in C bekommen Es ist nicht definiertes Verhalten ohne Diagnose erforderlich
MM
5

Die statische Deklaration auf dieser Codeebene bedeutet, dass die Variable nur in der aktuellen Kompilierungseinheit sichtbar ist. Dies bedeutet, dass nur Code in diesem Modul diese Variable sieht.

Wenn Sie eine Header-Datei haben, die eine Variable als statisch deklariert, und dieser Header in mehreren C / CPP-Dateien enthalten ist, ist diese Variable für diese Module "lokal". Es gibt N Kopien dieser Variablen für die N Stellen, an denen der Header enthalten ist. Sie sind überhaupt nicht miteinander verwandt. Jeder Code in einer dieser Quelldateien verweist nur auf die Variable, die in diesem Modul deklariert ist.

In diesem speziellen Fall scheint das Schlüsselwort "statisch" keinen Vorteil zu bieten. Ich vermisse vielleicht etwas, aber es scheint keine Rolle zu spielen - ich habe noch nie so etwas gesehen.

Was das Inlining betrifft, ist die Variable in diesem Fall wahrscheinlich inlined, aber das liegt nur daran, dass sie als const deklariert ist. Der Compiler könnte eher auf Inline - Modul statischen Variablen, aber das ist abhängig von der Situation und der Code kompiliert wird. Es gibt keine Garantie dafür, dass der Compiler 'statics' inline.

Kennzeichen
quelle
Der Vorteil von 'static' besteht darin, dass Sie ansonsten mehrere Globals mit demselben Namen deklarieren, eines für jedes Modul, das den Header enthält. Wenn sich der Linker nicht beschwert, liegt das nur daran, dass er sich auf die Zunge beißt und höflich ist.
In diesem Fall ist das aufgrund constdes staticimplizit und damit optional. Die Konsequenz ist, dass es keine Anfälligkeit für Mehrfachdefinitionsfehler gibt, wie Mike F. behauptete.
underscore_d
2

Das C-Buch (kostenlos online) enthält ein Kapitel über Verknüpfungen, in dem die Bedeutung von "statisch" ausführlicher erläutert wird (obwohl die richtige Antwort bereits in anderen Kommentaren angegeben ist): http://publications.gbdirect.co.uk/c_book /chapter4/linkage.html

Jan de Vos
quelle
2

Um die Frage zu beantworten: "Bedeutet statisch, dass nur eine Kopie von VAL erstellt wird, falls der Header in mehr als einer Quelldatei enthalten ist?" ...

NEIN . VAL wird in jeder Datei, die den Header enthält, immer separat definiert.

Die Standards für C und C ++ verursachen in diesem Fall einen Unterschied.

In C sind Variablen mit Dateibereich standardmäßig extern. Wenn Sie C verwenden, ist VAL statisch und ANOTHER_VAL ist extern.

Beachten Sie, dass moderne Linker sich möglicherweise über ANOTHER_VAL beschweren, wenn der Header in verschiedenen Dateien enthalten ist (derselbe globale Name wurde zweimal definiert), und sich definitiv beschweren würden, wenn ANOTHER_VAL mit einem anderen Wert in einer anderen Datei initialisiert wurde

In C ++ sind Variablen mit Dateibereich standardmäßig statisch, wenn sie const sind, und extern standardmäßig, wenn dies nicht der Fall ist. Wenn Sie C ++ verwenden, sind sowohl VAL als auch ANOTHER_VAL statisch.

Sie müssen auch die Tatsache berücksichtigen, dass beide Variablen als const bezeichnet werden. Im Idealfall würde der Compiler diese Variablen immer inline verwenden und keinen Speicher für sie einschließen. Es gibt eine ganze Reihe von Gründen, warum Speicher zugewiesen werden kann. Eine, an die ich denken kann ...

  • Debug-Optionen
  • Adresse in der Datei genommen
  • Der Compiler weist immer Speicher zu (komplexe Konstantentypen können nicht einfach eingefügt werden und werden daher zu einem Sonderfall für Basistypen).
itj
quelle
Hinweis: In der abstrakten Maschine befindet sich in jeder separaten Übersetzungseinheit, die den Header enthält, eine Kopie von VAL. In der Praxis kann der Linker entscheiden, sie trotzdem zu kombinieren, und der Compiler kann einige oder alle zuerst optimieren.
MM
1

Angenommen, diese Deklarationen haben einen globalen Geltungsbereich (dh sind keine Mitgliedsvariablen), dann:

statisch bedeutet "interne Verknüpfung". In diesem Fall kann dies vom Compiler optimiert / eingefügt werden , da es als const deklariert ist . Wenn Sie die const weglassen muss der Compiler Speicher in jeder Kompilierungseinheit zuweisen.

Wenn Sie statisch weglassen, erfolgt die Verknüpfung standardmäßig extern . Auch hier wurden Sie durch die Konstanz gerettet - der Compiler kann die Nutzung optimieren / inline. Wenn Sie die Konstante löschen , wird zum Zeitpunkt der Verknüpfung ein mehrfach definierter Symbolfehler angezeigt.

Seb Rose
quelle
Ich glaube, der Compiler muss in jedem Fall Platz für eine const int zuweisen, da ein anderes Modul immer "extern const int was auch immer; etwas (& was auch immer);" sagen könnte.
1

Sie können eine statische Variable nicht deklarieren, ohne sie ebenfalls zu definieren (dies liegt daran, dass sich die statischen und externen Speicherklassenmodifikatoren gegenseitig ausschließen). Eine statische Variable kann in einer Header-Datei definiert werden. Dies würde jedoch dazu führen, dass jede Quelldatei, die die Header-Datei enthält, eine eigene private Kopie der Variablen hat, was wahrscheinlich nicht beabsichtigt ist.

Gajendra Kumar
quelle
"... aber dies würde dazu führen, dass jede Quelldatei, die die Header-Datei enthält, eine eigene private Kopie der Variablen hat, was wahrscheinlich nicht beabsichtigt ist." - Aufgrund des Fiaskos der statischen Initialisierungsreihenfolge kann es erforderlich sein, in jeder Übersetzungseinheit eine Kopie zu haben.
JWW
1

const Variablen sind in C ++ standardmäßig statisch, aber extern C. Wenn Sie also C ++ verwenden, macht dies keinen Sinn, welche Konstruktion verwendet werden soll.

(7.11.6 C ++ 2003 und Apexndix C enthält Beispiele)

Beispiel für den Vergleich von Kompilierungs- / Linkquellen als C- und C ++ - Programm:

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
quelle
Es ist sinnvoll, das noch einzubeziehen static. Es signalisiert die Absicht / das Bewusstsein dessen, was der Programmierer tut, und behält die Parität mit anderen Arten von Deklarationen (und, fwiw, C) bei, denen das Implizite fehlt static. Es ist wie das Einbeziehen virtualund in letzter Zeit overridein Deklarationen übergeordneter Funktionen - nicht notwendig, aber viel selbstdokumentierender und im letzteren Fall der statischen Analyse förderlich.
underscore_d
Ich bin absolut einverstanden. zB Was mich im wirklichen Leben betrifft, schreibe ich es immer explizit.
Bruziuz
"Wenn Sie also C ++ verwenden, macht dies keinen Sinn, welche Konstruktion verwendet werden soll ..." - Hmm ... Ich habe gerade ein Projekt kompiliert, das constnur für eine Variable in einem Header mit verwendet wird g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2). Es ergaben sich ungefähr 150 mehrfach definierte Symbole (eines für jede Übersetzungseinheit, in der der Header enthalten war). Ich denke , wir müssen entweder static, inlineoder ein anonymes / unbenannte Namespace die externe Bindung zu vermeiden.
jww
Ich habe Baby-Beispiel mit gcc-5.4 mit Deklaration const intinnerhalb des Namespace-Bereichs und im globalen Namespace versucht . Und es kompilierte und folgt der Regel „Objekte const deklarierte und nicht explizit deklarierte extern haben interne Bindung.““.... Vielleicht in Projekt in irgendeinem Grunde diese Header in C kompilierte Quellen enthielten, in denen die Regeln ganz anders.
bruziuz
@jww Ich habe ein Beispiel mit einem Verknüpfungsproblem für C und keinen Problemen für C ++
hochgeladen
0

Statisch verhindert, dass eine andere Kompilierungseinheit diese Variable auslagert, sodass der Compiler den Wert der Variablen, in dem sie verwendet wird, einfach "inline" und keinen Speicher für sie erstellen kann.

In Ihrem zweiten Beispiel kann der Compiler nicht davon ausgehen, dass eine andere Quelldatei sie nicht extern erstellt. Daher muss er diesen Wert tatsächlich irgendwo im Speicher speichern.

Jim Buck
quelle
-2

Statisch verhindert, dass der Compiler mehrere Instanzen hinzufügt. Dies wird beim # ifndef-Schutz weniger wichtig. Unter der Annahme, dass der Header in zwei separaten Bibliotheken enthalten ist und die Anwendung verknüpft ist, werden zwei Instanzen eingeschlossen.

Superpolock
quelle
Angenommen, mit "Bibliotheken" meinen Sie Übersetzungseinheiten , dann nein, Include-Guards tun absolut nichts, um mehrere Definitionen zu verhindern, da sie nur vor wiederholten Einschlüssen innerhalb derselben Übersetzungseinheit schützen . Sie tun also überhaupt nichts, um static"weniger wichtig" zu machen . und selbst mit beiden können Sie mehrere intern verknüpfte Definitionen erhalten, was wahrscheinlich nicht beabsichtigt ist.
underscore_d