Dichiarazioni di variabili nei file di intestazione: statiche o no?


91

Durante il refactoring di alcuni #definesmi sono imbattuto in dichiarazioni simili alle seguenti in un file di intestazione C ++:

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

La domanda è: quale differenza, se del caso, farà la statica? Nota che l'inclusione multipla delle intestazioni non è possibile a causa del classico #ifndef HEADER #define HEADER #endiftrucco (se è importante).

Lo statico significa che VALviene creata solo una copia di , nel caso in cui l'intestazione sia inclusa da più di un file sorgente?


Risposte:


107

Ciò staticsignifica che ci sarà una copia di VALcreato per ogni file sorgente in cui è incluso. Ma significa anche che più inclusioni non risulteranno in più definizioni di VALciò si scontreranno al momento del collegamento. In C, senza staticè necessario assicurarsi che sia definito un solo file sorgente VALmentre gli altri file sorgente lo hanno dichiarato extern. Di solito si farebbe questo definendolo (possibilmente con un inizializzatore) in un file sorgente e mettere la externdichiarazione in un file di intestazione.

static le variabili a livello globale sono visibili solo nel proprio file sorgente, indipendentemente dal fatto che siano arrivate tramite un include o che si trovassero nel file principale.


Nota dell'editore: in C ++, gli constoggetti senza né le parole chiave staticexternnella loro dichiarazione sono implicitamente static.


Sono un fan dell'ultima frase, incredibilmente utile. Non ho votato la risposta perché 42 è meglio. modifica: grammatica
RealDeal_EE'18

"Lo statico significa che ci sarà una copia di VAL creata per ogni file sorgente in cui è incluso." Ciò sembra implicare che ci sarebbero due copie di VAL se due file sorgente includessero il file di intestazione. Spero che non sia vero e che ci sia sempre una singola istanza di VAL, indipendentemente dal numero di file che includono l'intestazione.
Brent212

4
@ Brent212 Il compilatore non sa se una dichiarazione / definizione proviene da un file di intestazione o da un file principale. Quindi speri invano. Ci saranno due copie di VAL se qualcuno è stato stupido e ha inserito una definizione statica in un file di intestazione e questa è stata inclusa in due sorgenti.
Justsalt

1
i valori const hanno un collegamento interno in C ++
adrianN

112

I tag statice externsulle variabili con ambito di file determinano se sono accessibili in altre unità di traduzione (cioè altro .co .cppfile).

  • staticfornisce il collegamento interno variabile, nascondendolo da altre unità di traduzione. Tuttavia, le variabili con collegamento interno possono essere definite in più unità di traduzione.

  • externfornisce il collegamento esterno variabile, rendendolo visibile ad altre unità di traduzione. Tipicamente questo significa che la variabile deve essere definita solo in un'unità di traduzione.

L'impostazione predefinita (quando non specifichi statico extern) è una di quelle aree in cui C e C ++ differiscono.

  • In C, le variabili con ambito di file sono extern(collegamento esterno) per impostazione predefinita. Se stai usando C, VALè staticed ANOTHER_VALè extern.

  • In C ++, le variabili con ambito di file sono static(collegamento interno) per impostazione predefinita se lo sono conste externper impostazione predefinita se non lo sono. Se stai usando C ++, entrambi VALe ANOTHER_VALsono static.

Da una bozza della specifica C :

6.2.2 Collegamenti di identificatori ... -5- Se la dichiarazione di un identificatore per una funzione non ha uno specificatore di classe di archiviazione, il suo collegamento è determinato esattamente come se fosse dichiarato con l'identificatore di classe di archiviazione extern. Se la dichiarazione di un identificatore per un oggetto ha un ambito di file e nessun identificatore di classe di archiviazione, il suo collegamento è esterno.

Da una bozza della specifica C ++ :

7.1.1 - Identificatori della classe di archiviazione [dcl.stc] ... -6- Un nome dichiarato in un ambito dello spazio dei nomi senza uno specificatore della classe di archiviazione ha un collegamento esterno a meno che non abbia un collegamento interno a causa di una dichiarazione precedente e purché non lo sia const dichiarato. Gli oggetti dichiarati const e non esplicitamente dichiarati extern hanno un collegamento interno.


47

Lo statico significherà che otterrai una copia per file, ma a differenza di altri hanno detto che è perfettamente legale farlo. Puoi facilmente testarlo con un piccolo esempio di codice:

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

L'esecuzione di questo ti dà questo output:

0x446020
0x446040


5
Grazie per l'esempio!
Kyrol

Mi chiedo se lo TESTfosse const, se LTO sarebbe in grado di ottimizzarlo in una singola posizione di memoria. Ma -O3 -fltodi GCC 8.1 no.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Sarebbe illegale farlo, anche se è costante, statico garantisce che ogni istanza sia locale rispetto all'unità di compilazione. Tuttavia, potrebbe probabilmente inline il valore della costante stessa se usato come costante, ma poiché prendiamo il suo indirizzo deve restituire un puntatore univoco.
lime a fette il

6

constle variabili in C ++ hanno un collegamento interno. Quindi, l'utilizzo staticnon ha alcun effetto.

ah

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

two.cpp

#include "a.h"

func1()
{
   cout << i;
}

Se questo fosse un programma C, si otterrebbe un errore di "definizione multipla" per i(a causa di un collegamento esterno).


2
Bene, l'uso staticha l'effetto che segnala chiaramente l'intenzione e la consapevolezza di ciò che si sta codificando, il che non è mai una cosa negativa. Per me è come includere virtualquando si esegue l'override: non dobbiamo, ma le cose sembrano molto più intuitive - e coerenti con altre dichiarazioni - quando lo facciamo.
underscore_d

È possibile che venga visualizzato un errore di definizione multipla in C. È un comportamento non definito senza necessità di diagnostica
MM

5

La dichiarazione statica a questo livello di codice significa che la variabile è visibile solo nell'unità di compilazione corrente. Ciò significa che solo il codice all'interno di quel modulo vedrà quella variabile.

se si dispone di un file di intestazione che dichiara una variabile statica e tale intestazione è inclusa in più file C / CPP, quella variabile sarà "locale" per quei moduli. Ci saranno N copie di quella variabile per le N posizioni in cui è inclusa l'intestazione. Non sono affatto collegati tra loro. Qualsiasi codice all'interno di uno qualsiasi di questi file sorgente farà riferimento solo alla variabile dichiarata all'interno di quel modulo.

In questo caso particolare, la parola chiave "statica" non sembra fornire alcun vantaggio. Potrei perdere qualcosa, ma sembra non avere importanza: non ho mai visto niente di simile prima.

Per quanto riguarda l'inlining, in questo caso la variabile è probabilmente inline, ma solo perché è dichiarata const. Il compilatore potrebbe avere maggiori probabilità di incorporare le variabili statiche del modulo, ma ciò dipende dalla situazione e dal codice da compilare. Non vi è alcuna garanzia che il compilatore includerà "statics" inline.


Il vantaggio di "statico" qui è che altrimenti dichiari più variabili globali tutte con lo stesso nome, una per ogni modulo che include l'intestazione. Se il linker non si lamenta è solo perché si morde la lingua ed è educato.

In questo caso, a causa di const, il staticè implicito e quindi opzionale. Il corollario è che non c'è suscettibilità a più errori di definizione come ha affermato Mike F.
underscore_d


2

Per rispondere alla domanda, "lo statico significa che è stata creata solo una copia di VAL, nel caso in cui l'intestazione sia inclusa da più di un file sorgente?" ...

NO . VAL sarà sempre definito separatamente in ogni file che include l'intestazione.

Gli standard per C e C ++ causano una differenza in questo caso.

In C, le variabili con ambito file sono esterne per impostazione predefinita. Se stai usando C, VAL è statico e ANOTHER_VAL è esterno.

Nota che i linker moderni possono lamentarsi di ANOTHER_VAL se l'intestazione è inclusa in file diversi (lo stesso nome globale definito due volte), e si lamenterebbero sicuramente se ANOTHER_VAL fosse inizializzato con un valore diverso in un altro file

In C ++, le variabili con ambito di file sono statiche per impostazione predefinita se sono const, ed esterne per impostazione predefinita se non lo sono. Se stai usando C ++, sia VAL che ANOTHER_VAL sono statici.

È inoltre necessario tenere conto del fatto che entrambe le variabili sono designate const. Idealmente, il compilatore sceglierebbe sempre di incorporare queste variabili e non includere alcuna memoria per esse. C'è tutta una serie di ragioni per cui è possibile allocare lo spazio di archiviazione. Quelli a cui riesco a pensare ...

  • opzioni di debug
  • indirizzo preso nel file
  • il compilatore alloca sempre lo spazio di archiviazione (i tipi const complessi non possono essere facilmente inline, quindi diventa un caso speciale per i tipi di base)

Nota: nella macchina astratta c'è una copia di VAL in ogni unità di traduzione separata che include l'intestazione. In pratica il linker potrebbe decidere di combinarli comunque e il compilatore potrebbe ottimizzarli prima alcuni o tutti.
MM

1

Supponendo che queste dichiarazioni siano di portata globale (cioè non sono variabili membro), quindi:

statico significa "collegamento interno". In questo caso, poiché è dichiarato const, può essere ottimizzato / integrato dal compilatore. Se si omette la const, il compilatore deve allocare memoria in ciascuna unità di compilazione.

Omettendo statico, il collegamento è esterno per impostazione predefinita. Ancora una volta, sei stato salvato dal const ness: il compilatore può ottimizzare / utilizzare in linea. Se si rilascia la const , verrà visualizzato un errore di simboli definiti in modo multiplo al momento del collegamento.


Credo che il compilatore debba allocare spazio per un const int in tutti i casi, poiché un altro modulo potrebbe sempre dire "extern const int qualunque cosa; qualcosa (& qualunque cosa);"

1

Non è possibile dichiarare una variabile statica senza definirla (questo perché i modificatori della classe di archiviazione static ed extern si escludono a vicenda). Una variabile statica può essere definita in un file di intestazione, ma questo farebbe sì che ogni file di origine che includeva il file di intestazione abbia la propria copia privata della variabile, che probabilmente non è ciò che era previsto.


"... ma questo farebbe sì che ogni file di origine che include il file di intestazione abbia la propria copia privata della variabile, che probabilmente non è ciò che era previsto." - A causa del fiasco dell'ordine di inizializzazione statico , potrebbe essere necessario disporre di una copia in ciascuna unità di traduzione.
jww

1

const variabili sono di default statiche in C ++, ma esterne C. Quindi se usi C ++ non ha senso quale costruzione usare.

(7.11.6 C ++ 2003 e Apexndix C ha esempi)

Esempio nel confronto di sorgenti di compilazione / collegamento come programmi C e C ++:

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

V'è senso ancora compreso il static. Segnala l'intento / la consapevolezza di ciò che il programmatore sta facendo e mantiene la parità con altri tipi di dichiarazione (e, in seguito, C) che mancano dell'implicito static. È come includere virtuale ultimamente overridenelle dichiarazioni di funzioni di override - non necessario, ma molto più auto-documentante e, nel caso di quest'ultimo, favorevole all'analisi statica.
underscore_d

Sono assolutamente d'accordo. es. Per quanto riguarda me nella vita reale, lo scrivo sempre esplicitamente.
bruziuz

"Quindi, se usi C ++, questo non ha senso quale costruzione usare ..." - Hmm ... Ho appena compilato un progetto che utilizzava constsolo una variabile in un'intestazione con g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2). Il risultato è stato di circa 150 simboli definiti in modo multiplo (uno per ciascuna unità di traduzione inclusa nell'intestazione). Penso che abbiamo bisogno sia static, inlineo un / namespace anonimo anonimo per evitare il collegamento esterno.
jww

Ho provato baby-example con gcc-5.4 con dichiarazione const intall'interno dell'ambito dello spazio dei nomi e nello spazio dei nomi globale. Ed è compilato e segue la regola "Gli oggetti dichiarati const e non esplicitamente dichiarati extern hanno un collegamento interno." ".... Forse nel progetto per qualche ragione questa intestazione includeva nei sorgenti compilati in C, dove le regole sono completamente diverse.
bruziuz

@jww Ho caricato un esempio con problema di collegamento per C e nessun problema per C ++
bruziuz

0

Statico impedisce a un'altra unità di compilazione di esternare quella variabile in modo che il compilatore possa semplicemente "inline" il valore della variabile dove viene utilizzata e non creare memoria per essa.

Nel secondo esempio, il compilatore non può presumere che un altro file sorgente non lo esternerà, quindi deve effettivamente memorizzare quel valore in memoria da qualche parte.


-2

Statico impedisce al compilatore di aggiungere più istanze. Questo diventa meno importante con la protezione #ifndef, ma supponendo che l'intestazione sia inclusa in due librerie separate e l'applicazione sia collegata, verrebbero incluse due istanze.


supponendo che per "biblioteche" si intenda unità di traduzione , allora no, le guardie di inclusione non fanno assolutamente nulla per impedire definizioni multiple, visto che si limitano a proteggersi da inclusioni ripetute all'interno della stessa unità di traduzione . quindi, non fanno nulla per rendere static"meno importante". e anche con entrambi, puoi ritrovarti con più definizioni collegate internamente, cosa che probabilmente non è prevista.
underscore_d
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.