Deklaracje zmiennych w plikach nagłówkowych - statyczne czy nie?

91

Podczas refaktoryzacji niektórych #definesnapotkałem deklaracje podobne do następujących w pliku nagłówkowym C ++:

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

Pytanie brzmi, jaką różnicę, jeśli w ogóle, zrobi statyczność? Zauważ, że wielokrotne dołączanie nagłówków nie jest możliwe z powodu klasycznej #ifndef HEADER #define HEADER #endifsztuczki (jeśli to ma znaczenie).

Czy statyczny oznacza, że VALtworzona jest tylko jedna kopia , w przypadku gdy nagłówek jest zawarty w więcej niż jednym pliku źródłowym?

Obrabować
źródło
related: stackoverflow.com/questions/177437/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Odpowiedzi:

107

Te staticśrodki, które będzie jedna kopia VALstworzona dla każdego pliku źródłowego, to jest wliczone w. Ale oznacza to również, że wiele wtrącenia nie spowoduje w wielu definicjach VAL, które zderzają się w momencie połączenia. W C, bez tego static, musiałbyś upewnić się, że tylko jeden plik źródłowy jest zdefiniowany, VALpodczas gdy inne pliki źródłowe go deklarują extern. Zwykle można to zrobić, definiując go (prawdopodobnie z inicjatorem) w pliku źródłowym i umieszczając externdeklarację w pliku nagłówkowym.

static zmienne na poziomie globalnym są widoczne tylko w ich własnym pliku źródłowym, niezależnie od tego, czy dostały się tam poprzez dołączenie, czy też znajdowały się w pliku głównym.


Uwaga edytora: W C ++ constobiekty bez słów kluczowych staticani externw swojej deklaracji są niejawnie static.

Justsalt
źródło
Jestem fanem ostatniego zdania, niezwykle pomocnego. Nie głosowałem na odpowiedź, bo 42 jest lepsze. edycja: gramatyka
RealDeal_EE'18
„Wartość statyczna oznacza, że ​​zostanie utworzona jedna kopia wartości VAL dla każdego pliku źródłowego, w którym się znajduje”. Wydaje się to sugerować, że byłyby dwie kopie VAL, gdyby dwa pliki źródłowe zawierały plik nagłówkowy. Mam nadzieję, że to nieprawda i że zawsze istnieje jedno wystąpienie VAL, niezależnie od tego, ile plików zawiera nagłówek.
Brent212
4
@ Brent212 Kompilator nie wie, czy deklaracja / definicja pochodzi z pliku nagłówkowego, czy z pliku głównego. Więc masz nadzieję na próżno. Będą dwie kopie VAL, jeśli ktoś był głupi i umieścił statyczną definicję w pliku nagłówkowym, a została ona uwzględniona w dwóch źródłach.
Justsalt,
1
wartości const mają wewnętrzne powiązanie w C ++
adrianN
112

Znaczniki statici w externzmiennych o zasięgu pliku określają, czy są one dostępne w innych jednostkach tłumaczeniowych (tj. Inne .club .cpppliki).

  • staticdaje zmiennej powiązanie wewnętrzne, ukrywając ją przed innymi jednostkami tłumaczeniowymi. Jednak zmienne z powiązaniami wewnętrznymi można definiować w wielu jednostkach tłumaczeniowych.

  • externdaje zmiennej link zewnętrzny, czyniąc ją widoczną dla innych jednostek tłumaczeniowych. Zwykle oznacza to, że zmienna musi być zdefiniowana tylko w jednej jednostce tłumaczeniowej.

Wartość domyślna (jeśli nie określisz staticlub extern) jest jednym z tych obszarów, w których C i C ++ różnią się.

  • W języku C zmienne o zasięgu pliku są externdomyślnie (łączenie zewnętrzne). Jeśli używasz C, VALjest statici ANOTHER_VALjest extern.

  • W C ++ zmienne o zasięgu pliku są staticdomyślnie (łączeniem wewnętrznym), jeśli tak const, i externdomyślnie, jeśli nie są. Jeśli używasz C ++, oba VALi ANOTHER_VALstatic.

Z projektu specyfikacji C :

6.2.2 Powiązania identyfikatorów ... -5- Jeśli deklaracja identyfikatora funkcji nie ma specyfikatora klasy pamięci, jej powiązanie jest określane dokładnie tak, jakby było zadeklarowane za pomocą specyfikatora klasy pamięci extern. Jeśli deklaracja identyfikatora obiektu ma zasięg plikowy i nie ma specyfikatora klasy pamięci, jej powiązanie jest zewnętrzne.

Z szkicu specyfikacji C ++ :

7.1.1 - Specyfikatory klasy pamięci masowej [dcl.stc] ... -6- Nazwa zadeklarowana w zakresie przestrzeni nazw bez specyfikatora klasy pamięci ma zewnętrzne połączenie, chyba że ma wewnętrzne powiązanie z powodu poprzedniej deklaracji i pod warunkiem, że nie jest deklarowana konst. Obiekty zadeklarowane jako const i nie zadeklarowane jawnie jako extern mają wewnętrzne powiązanie.

bk1e
źródło
47

Statyczny oznacza, że ​​otrzymasz jedną kopię na plik, ale w przeciwieństwie do innych osób powiedzieli, że jest to całkowicie legalne. Możesz to łatwo przetestować za pomocą małego przykładu kodu:

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

Uruchomienie tego daje następujący wynik:

0x446020
0x446040

wapno w plasterkach
źródło
5
Dzięki za przykład!
Kyrol
Zastanawiam się, czy TESTtak const, czy LTO byłoby w stanie zoptymalizować to w jednym miejscu pamięci. Ale -O3 -fltoz GCC 8.1 nie.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Byłoby to nielegalne - nawet jeśli jest stała, statyczna gwarancja, że ​​każda instancja jest lokalna dla jednostki kompilacji. Prawdopodobnie mógłby on sam wstawić stałą wartość, gdyby był używany jako stała, ale ponieważ bierzemy jego adres, musi zwracać unikalny wskaźnik.
wapno w plasterkach
6

constzmienne w C ++ mają wewnętrzne powiązania. Tak więc używanie staticnie ma żadnego efektu.

ah

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

two.cpp

#include "a.h"

func1()
{
   cout << i;
}

Gdyby to był program w C, wystąpiłby błąd „wielokrotnej definicji” dla i(z powodu zewnętrznego powiązania).

Nitin
źródło
2
Cóż, używanie staticma taki efekt, że starannie sygnalizuje zamiar i świadomość tego, co kodujemy, co nigdy nie jest złe. Dla mnie jest to tak, jakby uwzględniać virtualprzy nadpisywaniu: nie musimy, ale rzeczy wyglądają o wiele bardziej intuicyjnie - i spójnie z innymi deklaracjami - kiedy to robimy.
underscore_d
Państwo może uzyskać wiele Błąd definicji w C. To jest niezdefiniowane zachowanie bez wymaganego diagnostycznego
MM
5

Deklaracja statyczna na tym poziomie kodu oznacza, że ​​zmienna jest widoczna tylko w bieżącej jednostce kompilacji. Oznacza to, że tylko kod w tym module będzie widział tę zmienną.

jeśli masz plik nagłówkowy, który deklaruje zmienną jako statyczną, a ten nagłówek jest zawarty w wielu plikach C / CPP, wówczas zmienna będzie „lokalna” dla tych modułów. Będzie N kopii tej zmiennej dla N miejsc, w których znajduje się nagłówek. W ogóle nie są ze sobą spokrewnieni. Każdy kod w dowolnym z tych plików źródłowych będzie odwoływał się tylko do zmiennej zadeklarowanej w tym module.

W tym konkretnym przypadku słowo kluczowe „static” nie wydaje się przynosić żadnych korzyści. Mogę czegoś przeoczyć, ale wydaje się, że nie ma to znaczenia - nigdy wcześniej nie widziałem czegoś takiego.

Jeśli chodzi o wstawianie, w tym przypadku zmienna jest prawdopodobnie wstawiana, ale to tylko dlatego, że jest zadeklarowana jako stała. Kompilator może z większym prawdopodobieństwem wbudować statyczne zmienne modułu, ale zależy to od sytuacji i kompilowanego kodu. Nie ma gwarancji, że kompilator będzie zawierał „statykę”.

znak
źródło
Zaletą „statycznego” jest to, że w przeciwnym razie deklarujesz wiele zmiennych globalnych o tej samej nazwie, po jednym dla każdego modułu zawierającego nagłówek. Jeśli linker nie narzeka, to tylko dlatego, że gryzie język i jest grzeczny.
W tym przypadku, ze względu na const, staticjest domniemane, a zatem opcjonalne. Konsekwencją jest to, że nie ma podatności na wielokrotne błędy definicji, jak twierdził Mike F.
underscore_d
2

Książka C (dostępna bezpłatnie online) zawiera rozdział o powiązaniach, w którym bardziej szczegółowo wyjaśnia się znaczenie słowa „statyczny” (chociaż poprawną odpowiedź podano już w innych komentarzach): http://publications.gbdirect.co.uk/c_book /chapter4/linkage.html

Jan de Vos
źródło
2

Aby odpowiedzieć na pytanie, „czy statyczny oznacza, że ​​tworzona jest tylko jedna kopia VAL, w przypadku gdy nagłówek jest zawarty w więcej niż jednym pliku źródłowym?” ...

NIE . VAL będzie zawsze definiowane oddzielnie w każdym pliku zawierającym nagłówek.

Standardy C i C ++ powodują różnicę w tym przypadku.

W języku C zmienne o zasięgu pliku są domyślnie extern. Jeśli używasz C, VAL jest statyczne, a ANOTHER_VAL to zewnętrzne.

Zauważ, że nowoczesne konsolidatory mogą narzekać na ANOTHER_VAL, jeśli nagłówek jest zawarty w różnych plikach (ta sama nazwa globalna zdefiniowana dwukrotnie) i na pewno skarżyliby się, gdyby ANOTHER_VAL został zainicjowany inną wartością w innym pliku

W języku C ++ zmienne o zasięgu pliku są domyślnie statyczne, jeśli są stałymi, i domyślnie extern, jeśli nie są. Jeśli używasz C ++, zarówno VAL, jak i ANOTHER_VAL są statyczne.

Musisz również wziąć pod uwagę fakt, że obie zmienne są oznaczone jako const. Idealnie byłoby, gdyby kompilator zawsze wybierał wstawianie tych zmiennych i nie zawierał dla nich pamięci. Istnieje wiele powodów, dla których można przydzielić pamięć. Te, które przychodzą mi do głowy ...

  • opcje debugowania
  • adres wzięty w pliku
  • kompilator zawsze przydziela pamięć (złożone typy const nie mogą być łatwo wstawiane, więc staje się specjalnym przypadkiem dla typów podstawowych)
itj
źródło
Uwaga: W maszynie abstrakcyjnej znajduje się jedna kopia VAL w każdej oddzielnej jednostce tłumaczeniowej, która zawiera nagłówek. W praktyce linker może i tak zdecydować o ich połączeniu, a kompilator może najpierw zoptymalizować niektóre lub wszystkie z nich.
MM
1

Zakładając, że te deklaracje mają zasięg globalny (tj. Nie są zmiennymi składowymi), to:

statyczny oznacza „wewnętrzne powiązanie”. W tym przypadku, ponieważ jest zadeklarowane jako const, może to zostać zoptymalizowane / wstawione przez kompilator. Jeśli pominiesz stałą, kompilator musi przydzielić pamięć w każdej jednostce kompilacji.

Pomijając statyczne wiązanie jest extern domyślnie. Ponownie, już został zapisany przez const Ness - kompilator może zoptymalizować / inline użytkowania. Jeśli upuścisz stałą , otrzymasz błąd wielokrotnie zdefiniowanych symboli w czasie łączenia.

Seb Rose
źródło
Uważam, że kompilator musi przydzielić miejsce na stałą int we wszystkich przypadkach, ponieważ inny moduł może zawsze powiedzieć „extern const int cokolwiek; coś (i cokolwiek);”
1

Nie można zadeklarować zmiennej statycznej bez jej definiowania (dzieje się tak, ponieważ modyfikatory klasy magazynu static i extern wzajemnie się wykluczają). Zmienną statyczną można zdefiniować w pliku nagłówkowym, ale spowodowałoby to, że każdy plik źródłowy zawierający plik nagłówkowy miałby swoją prywatną kopię zmiennej, co prawdopodobnie nie było tym, co było zamierzone.

Gajendra Kumar
źródło
„... ale to spowodowałoby, że każdy plik źródłowy, który zawierałby plik nagłówkowy, miałby swoją prywatną kopię zmiennej, co prawdopodobnie nie jest tym, co było zamierzone”. - Ze względu na fiasko statycznego zlecenia inicjalizacji , może być wymagane posiadanie kopii w każdej jednostce tłumaczeniowej.
jww
1

zmienne const są domyślnie statyczne w C ++, ale zewnętrzne C. Więc jeśli używasz C ++, nie ma sensu, jakiej konstrukcji użyć.

(7.11.6 C ++ 2003, a Apexndix C ma próbki)

Przykład porównania źródeł kompilacji / linków jako programu C i 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
bruziuz
źródło
Nie ma sensu w dalszym ciągu w tym static. Sygnalizuje zamiar / świadomość tego, co robi programista i zachowuje zgodność z innymi typami deklaracji (i fwiw, C), które nie mają tego ukrytego static. To jak włączanie virtuali ostatnio overridedo deklaracji funkcji nadrzędnych - nie jest to konieczne, ale dużo bardziej samodokumentujące, aw przypadku tych drugich sprzyjające analizie statycznej.
underscore_d
Absolutnie się zgadzam. np. Jeśli chodzi o mnie w życiu, zawsze piszę to wyraźnie.
bruziuz
"Więc jeśli używasz C ++, to nie ma sensu jakiej konstrukcji użyć ..." - Hmm ... Właśnie skompilowałem projekt, który używał consttylko zmiennej w nagłówku z g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2). W rezultacie powstało około 150 wielokrotnie zdefiniowanych symboli (po jednym na każdą jednostkę tłumaczeniową z dołączonym nagłówkiem). Myślę, że musimy albo static, inlinealbo / namespace nienazwanych anonimowy uniknąć powiązań zewnętrznych.
jww
Próbowałem baby-example z gcc-5.4 z deklaracją const intw zakresie przestrzeni nazw i globalnej przestrzeni nazw. I jest skompilowany i postępuje zgodnie z zasadą "Obiekty zadeklarowane jako const, a nie jawnie zadeklarowane jako extern mają wewnętrzne powiązanie." ".... Może w projekcie z jakiegoś powodu ten nagłówek został włączony do skompilowanych źródeł C, gdzie reguły są zupełnie inne.
bruziuz
@jww Wgrałem przykład z problemem powiązania dla C i brak problemów dla C ++
bruziuz
0

Static uniemożliwia innej jednostce kompilacji externowanie tej zmiennej, tak aby kompilator mógł po prostu „wstawić” wartość zmiennej w miejscu jej użycia i nie tworzyć dla niej pamięci.

W drugim przykładzie kompilator nie może założyć, że jakiś inny plik źródłowy nie będzie go extern, więc musi faktycznie gdzieś przechowywać tę wartość w pamięci.

Jim Buck
źródło
-2

Statyczny zapobiega dodawaniu przez kompilator wielu wystąpień. Staje się to mniej ważne w przypadku ochrony #ifndef, ale zakładając, że nagłówek znajduje się w dwóch oddzielnych bibliotekach, a aplikacja jest połączona, uwzględnione zostaną dwie instancje.

Superpolock
źródło
zakładając, że przez „biblioteki” masz na myśli jednostki tłumaczeniowe , to nie, strażnicy włączający nie robią absolutnie nic, aby zapobiec wielokrotnym definicjom, ponieważ chronią jedynie przed powtarzającymi się włączeniami w tej samej jednostce tłumaczeniowej . więc nie robią nic, aby uczynić static„mniej ważnym”. a nawet z obydwoma możesz skończyć z wieloma wewnętrznie połączonymi definicjami, co prawdopodobnie nie jest zamierzone.
underscore_d