Quando refactoring via alcuni #defines
Mi 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 farà l'eventuale statica? Tieni presente che l'inclusione multipla delle intestazioni non è possibile a causa del classico #ifndef HEADER
#define HEADER
#endif
trucco (se è importante).
Lo statico significa che viene creata una sola copia di VAL
, nel caso in cui l'intestazione sia inclusa da più di un file sorgente?
static
indica che verrà creata una copia di VAL
per ogni file di origine in cui è incluso. Ma significa anche che inclusioni multiple non comporteranno definizioni multiple di VAL
che si scontrerà al momento del collegamento. In C, senza static
dovresti assicurarti che solo un file sorgente abbia definito VAL
mentre gli altri file sorgente lo hanno dichiarato extern
. Di solito uno lo farebbe definendolo (possibilmente con un inizializzatore) in un file sorgente e inserendo la dichiarazione extern
in un file di intestazione.
static
Le variabili a livello globale sono visibili nel proprio file sorgente solo se sono arrivate lì tramite un'inclusione o erano nel file principale.
Nota del redattore: In C++, gli oggetti const
senza le parole chiave static
né extern
nella loro dichiarazione sono implicitamente static
.
I tag static
e extern
sulle variabili con ambito file determinano se sono accessibili in altre unità di traduzione (ovvero altri .c
o .cpp
File).
static
fornisce il collegamento interno variabile, nascondendolo da altre unità di traduzione. Tuttavia, le variabili con collegamento interno possono essere definite in più unità di traduzione.
extern
fornisce il collegamento esterno variabile, rendendolo visibile ad altre unità di traduzione. In genere ciò significa che la variabile deve essere definita in una sola unità di traduzione.
L'impostazione predefinita (quando non si specifica static
o extern
) è una di quelle aree in cui C e C++ differiscono.
In C, le variabili con ambito file sono extern
(collegamento esterno) per impostazione predefinita. Se stai usando C, VAL
è static
e ANOTHER_VAL
è extern
.
In C++, le variabili con ambito file sono static
(collegamento interno) per impostazione predefinita se sono const
e extern
per impostazione predefinita se non lo sono. Se stai usando C++, sia VAL
che ANOTHER_VAL
sono static
.
Da una bozza del specifica C :
6.2.2 Collegamenti di identificatori ... -5- Se la dichiarazione di un identificatore per una funzione non ha un identificatore della classe di archiviazione, il suo collegamento viene determinato esattamente come se fosse dichiarato con l'identificatore della classe di archiviazione esterno. Se la dichiarazione di un identificatore per un oggetto ha un ambito file e nessun identificatore della classe di archiviazione, il suo collegamento è esterno.
Da una bozza del specifica C++ :
7.1.1 - Identificatori della classe di archiviazione [dcl.stc] ... -6- Un nome dichiarato in un ambito dello spazio dei nomi senza un identificatore 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 dichiarata Gli oggetti dichiarati const e non esplicitamente dichiarati extern hanno un collegamento interno.
La statica 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
const
le variabili in C++ hanno un collegamento interno. Quindi, l'uso di static
non ha alcun effetto.
a.h
const int i = 10;
one.cpp
#include "a.h"
func()
{
cout << i;
}
two.cpp
#include "a.h"
func1()
{
cout << i;
}
Se si trattasse di un programma C, si otterrebbe l'errore di "definizione multipla" per i
(a causa del collegamento esterno).
La dichiarazione statica a questo livello di codice indica che la variabel è 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, tale variabile sarà "locale" per tali 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 di questi file di origine 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 mancare qualcosa, ma sembra non avere importanza: non ho mai visto nulla di simile prima d'ora.
Per quanto riguarda l'inline, in questo caso la variabile è probabilmente inline, ma è solo perché è dichiarata const. Il compilatore potrebbe ha maggiori probabilità di incorporare le variabili statiche del modulo, ma ciò dipende dalla situazione e dal codice che viene compilato. Non vi è alcuna garanzia che il compilatore includa "statica".
Per rispondere alla domanda "lo statico significa che viene creata una sola copia di VAL, nel caso in cui l'intestazione sia inclusa da più di un file sorgente?" ...
[~ ~ #] non [~ ~ #]. VAL sarà sempre definito separatamente in ogni file che include l'intestazione.
Gli standard per C e C++ fanno la 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.
Si noti che i linker moderni potrebbero lamentarsi di ANOTHER_VAL se l'intestazione è inclusa in file diversi (stesso nome globale definito due volte) e si lamenterebbero sicuramente se ANOTHER_VAL fosse inizializzato su un valore diverso in un altro file
In C++, le variabili con ambito file sono statiche per impostazione predefinita se sono const ed extern 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 di non includere alcuna memoria per esse. Esistono molti motivi per cui è possibile allocare memoria. Quelli a cui riesco a pensare ...
Il libro C (gratuito online) ha un capitolo sul collegamento, che spiega più dettagliatamente il significato di "statico" (sebbene la risposta corretta sia già fornita in altri commenti): http://publications.gbdirect.co .uk/c_book/Capitolo 4/linkage.html
Non puoi dichiarare una variabile statica anche senza definirla (questo perché i modificatori della classe di archiviazione statici ed esterni si escludono a vicenda). Una variabile statica può essere definita in un file di intestazione, ma ciò farebbe sì che ogni file di origine che includeva il file di intestazione avesse la propria copia privata della variabile, che probabilmente non è ciò che era previsto.
Supponendo che queste dichiarazioni abbiano portata globale (ovvero non siano variabili membro), quindi:
statico significa "collegamento interno". In questo caso, poiché è dichiarato const questo può essere ottimizzato/integrato dal compilatore. Se si omette 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 da const ness - il compilatore può ottimizzare/utilizzare in linea. Se si rilascia const, si otterrà un errore moltiplicato per i simboli al momento del collegamento.
const le variabili sono di default statiche in C++, ma esternamente C. Quindi se usi C++ questo non ha senso quale costruzione usare.
(7.11.6 C++ 2003 e Apexndix C ha degli esempi)
Esempio nel confrontare le fonti di compilazione/collegamento come programma 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
Statico impedisce a un'altra unità di compilazione di esternare quella variabile in modo che il compilatore possa semplicemente "incorporare" il valore della variabile in cui viene utilizzata e non creare memoria per essa.
Nel tuo secondo esempio, il compilatore non può presumere che qualche altro file sorgente non lo esternerà, quindi deve effettivamente archiviare quel valore in memoria da qualche parte.