Co oznacza „const static” w C i C ++?

117
const static int foo = 42;

Widziałem to w kodzie tutaj w StackOverflow i nie mogłem zrozumieć, co robi. Potem zobaczyłem zagmatwane odpowiedzi na innych forach. Domyślam się, że jest używany w C, aby ukryć stałą fooprzed innymi modułami. Czy to jest poprawne? Jeśli tak, po co ktoś miałby go używać w kontekście C ++, w którym można to zrobić private?

c0m4
źródło

Odpowiedzi:

113

Ma zastosowania zarówno w C, jak i C ++.

Jak się domyślasz, staticczęść ogranicza swój zakres do tej jednostki kompilacji . Zapewnia również statyczną inicjalizację. constpo prostu mówi kompilatorowi, aby nie pozwalał nikomu go modyfikować. Ta zmienna jest umieszczana w segmencie danych lub bss w zależności od architektury i może znajdować się w pamięci oznaczonej jako tylko do odczytu.

To wszystko w jaki sposób C traktuje te zmienne (lub jak C ++ traktuje zmienne przestrzeni nazw). W C ++ oznaczony element członkowski staticjest współużytkowany przez wszystkie wystąpienia danej klasy. To, czy jest prywatna, czy nie, nie wpływa na fakt, że jedna zmienna jest współdzielona przez wiele instancji. Obecność consttam ostrzeże Cię, jeśli jakikolwiek kod będzie próbował to zmodyfikować.

Gdyby była ściśle prywatna, każda instancja klasy otrzymałaby swoją własną wersję (niezależnie od optymalizatora).

Chris Arguin
źródło
1
Oryginalny przykład mówi o „zmiennej prywatnej”. Dlatego jest to mebmer, a statyczność nie ma wpływu na połączenie. Należy usunąć „część statyczna ogranicza swój zakres do tego pliku”.
Richard Corden
„Specjalna sekcja” jest nazywana segmentem danych i dzieli ją ze wszystkimi innymi zmiennymi globalnymi, takimi jak jawne „ciągi znaków” i tablice globalne. Jest to sprzeczne z segmentem kodu.
spoulson,
@Richard - co sprawia, że ​​myślisz, że jest członkiem klasy? W pytaniu nic nie mówi, że tak jest. Jeśli jest członkiem klasy, to masz rację, ale jeśli jest to tylko zmienna zadeklarowana w zasięgu globalnym, to Chris ma rację.
Graeme Perrow
1
Oryginalny plakat wspomniał o prywatnym jako możliwym lepszym rozwiązaniu, ale nie jako o pierwotnym problemie.
Chris Arguin
@Graeme, OK, więc nie jest to „zdecydowanie” członek - jednak ta odpowiedź polega na tworzeniu instrukcji, które mają zastosowanie tylko do elementów członkowskich przestrzeni nazw, a te instrukcje są nieprawidłowe dla zmiennych składowych. Biorąc pod uwagę liczbę głosów, błąd ten może zmylić kogoś niezbyt zaznajomionego z językiem - należy to naprawić.
Richard Corden
212

Wiele osób dało odpowiedź podstawowe, ale nikt nie zauważył, że w C ++ constdomyślnych aby staticna namespacepoziomie (i niektórzy dali błędne informacje). Zobacz standard C ++ 98 w sekcji 3.5.3.

Najpierw trochę tła:

Jednostka tłumacząca: plik źródłowy po preprocesorze (rekurencyjnie) zawierał wszystkie pliki dołączane.

Powiązanie statyczne: symbol jest dostępny tylko w jednostce tłumaczeniowej.

Powiązanie zewnętrzne: symbol jest dostępny w innych jednostkach tłumaczeniowych.

Na namespacepoziomie

Obejmuje to globalną przestrzeń nazw, czyli zmienne globalne .

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

Na poziomie funkcji

staticoznacza, że ​​wartość jest utrzymywana między wywołaniami funkcji.
Semantyka staticzmiennych funkcyjnych jest podobna do zmiennych globalnych, ponieważ znajdują się one w segmencie danych programu (a nie na stosie lub stercie). Zobacz to pytanie, aby uzyskać więcej informacji na temat czasu staticżycia zmiennych.

Na classpoziomie

staticoznacza, że ​​wartość jest wspólna dla wszystkich instancji klasy i constoznacza, że ​​się nie zmienia.

Motti
źródło
2
Na poziomie funkcji: statyczność nie jest zbędna ze stałą, mogą zachowywać się inaczej const int *foo(int x) {const int b=x;return &b};niżconst int *foo(int x) {static const int b=x;return &b};
Hanczar
1
Pytanie dotyczy zarówno C, jak i C ++, więc powinieneś dołączyć uwagę consttylko na temat sugerowania staticw tym drugim.
Nikolai Ruhe
@Motti: Świetna odpowiedź. Czy mógłbyś wyjaśnić, co sprawia, że ​​na poziomie funkcji jest to zbędne? Czy chcesz powiedzieć, że constdeklaracja implikuje również statictam? Tak jak w przypadku odrzucenia consti zmodyfikowania wartości, wszystkie wartości zostaną zmodyfikowane?
Cookie
1
@Motti constnie oznacza statyczności na poziomie funkcji, co byłoby koszmarem współbieżności (const! = Stałe wyrażenie), wszystko na poziomie funkcji jest niejawne auto. Ponieważ to pytanie jest również oznaczone jako [c], powinienem wspomnieć, że poziom globalny const intznajduje się niejawnie externw C. Jednak reguły, które tu masz, doskonale opisują C ++.
Ryan Haining
1
A w C ++ we wszystkich trzech przypadkach staticwskazuje, że zmienna ma statyczny czas trwania (istnieje tylko jedna kopia, która trwa od początku do końca programu) i ma wewnętrzne / statyczne powiązanie, jeśli nie określono inaczej (jest to nadpisywane przez funkcję połączenie dla lokalnych zmiennych statycznych lub połączenie klasy dla elementów statycznych). Główne różnice dotyczą tego, co to oznacza w każdej sytuacji, w której staticjest to ważne.
Justin Time - Przywróć Monikę
45

Ta linia kodu może faktycznie pojawić się w kilku różnych kontekstach i chociaż zachowuje się mniej więcej tak samo, istnieją niewielkie różnice.

Zakres przestrzeni nazw

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

i” będzie widoczny w każdej jednostce tłumaczeniowej zawierającej nagłówek. Jednak jeśli faktycznie nie użyjesz adresu obiektu (na przykład. „ &i”), Jestem prawie pewien, że kompilator potraktuje „ i” po prostu jako typ bezpieczny 0. Tam, gdzie dwie kolejne jednostki tłumaczeniowe przyjmą znak „ &i”, adres będzie inny dla każdej jednostki tłumaczeniowej.

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

i” ma wewnętrzne powiązanie, więc nie można się do niego odwoływać spoza tej jednostki tłumaczeniowej. Jednak ponownie, jeśli nie użyjesz jego adresu, najprawdopodobniej będzie traktowany jako bezpieczny dla typów 0.

Warto zwrócić uwagę, że następująca deklaracja:

const int i1 = 0;

jest dokładnie taki sam jak static const int i = 0. Zmienna w przestrzeni nazw zadeklarowana zi constnie zadeklarowana jawnie za pomocą externjest niejawnie statyczna. Jeśli się nad tym zastanowić, intencją komitetu C ++ było zezwolenie constna deklarowanie zmiennych w plikach nagłówkowych bez konieczności używania staticsłowa kluczowego, aby uniknąć zerwania ODR.

Zakres zajęć

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

W powyższym przykładzie norma wyraźnie określa, że ​​„ i” nie musi być definiowane, jeśli jego adres nie jest wymagany. Innymi słowy, jeśli używasz tylko „ i” jako bezpiecznego 0, kompilator nie zdefiniuje go. Jedną różnicą między wersjami klasy i przestrzeni nazw jest to, że adres „ i” (jeśli jest używany w dwóch lub więcej jednostkach tłumaczeniowych) będzie taki sam dla elementu klasy. Tam, gdzie używany jest adres, musisz mieć jego definicję:

// 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
źródło
2
+1 za wskazanie, że statyczna stała jest taka sama jak stała w zakresie przestrzeni nazw.
Plumenator
Właściwie nie ma różnicy pomiędzy umieszczeniem w „foo.h” lub „foo.cc”, ponieważ .h jest po prostu dołączane podczas kompilacji jednostki tłumaczeniowej.
Michaił
2
@Mikhail: Masz rację. Istnieje założenie, że nagłówek może znajdować się w wielu jednostkach tłumaczeniowych, dlatego warto było o tym porozmawiać osobno.
Richard Corden
24

To niewielka optymalizacja przestrzeni.

Kiedy powiesz

const int foo = 42;

Nie definiujesz stałej, ale tworzysz zmienną tylko do odczytu. Kompilator jest wystarczająco inteligentny, aby użyć 42, ilekroć zobaczy foo, ale przydzieli mu również miejsce w zainicjowanym obszarze danych. Dzieje się tak, ponieważ zgodnie z definicją foo ma zewnętrzne połączenie. Inna jednostka kompilacji może powiedzieć:

extern const int foo;

Aby uzyskać dostęp do jego wartości. To nie jest dobra praktyka, ponieważ ta jednostka kompilacji nie ma pojęcia, jaka jest wartość foo. Po prostu wie, że jest to const int i musi ponownie załadować wartość z pamięci za każdym razem, gdy jest używana.

Teraz, deklarując, że jest statyczny:

static const int foo = 42;

Kompilator może przeprowadzić swoją zwykłą optymalizację, ale może również powiedzieć „hej, nikt poza tą jednostką kompilacji nie widzi foo i wiem, że zawsze jest to 42, więc nie ma potrzeby przydzielania na to miejsca”.

Powinienem również zauważyć, że w C ++ preferowanym sposobem zapobiegania ucieczce nazw z bieżącej jednostki kompilacji jest użycie anonimowej przestrzeni nazw:

namespace {
    const int foo = 42; // same as static definition above
}
Ferruccio
źródło
1
, u wspomniano bez użycia statycznego „przydzieli również miejsce w zainicjowanym obszarze danych”. i używając statycznego „nie ma potrzeby przydzielania na to miejsca”. (skąd kompilator pobiera wtedy wartość?) czy możesz wyjaśnić w terminach stosu i sterty, gdzie jest przechowywana zmienna. Popraw mnie, jeśli ją interpretuję źle .
Nihar
@ N.Nihar - Statyczny obszar danych to fragment pamięci o stałym rozmiarze, który zawiera wszystkie dane, które są połączone statycznie. Jest „alokowany” przez proces ładowania programu do pamięci. Nie jest częścią stosu ani sterty.
Ferruccio
Co się stanie, jeśli funkcja zwraca wskaźnik do foo? Czy to psuje optymalizację?
nw.
@nw: Tak, musiałoby.
Ferruccio
8

Brakuje „int”. Powinno być:

const static int foo = 42;

W C i C ++ deklaruje stałą całkowitą z zakresem pliku lokalnego o wartości 42.

Dlaczego 42? Jeśli jeszcze nie wiesz (i trudno uwierzyć, że nie), jest to odniesienie do odpowiedzi na życie, wszechświat i wszystko .

Kevin
źródło
Dzięki ... teraz za każdym razem ... do końca mojego życia ... kiedy zobaczę 42 lata, zawsze będę się nad tym zastanawiać. haha
Inisheer
Jest to dowód na to, że wszechświat został stworzony przez bycie z 13 palcami (pytanie i odpowiedź faktycznie pasują do podstawy 13).
paxdiablo
To myszy. 3 palce na każdej stopie plus ogon daje podstawę 13.
KeithB,
Właściwie nie potrzebujesz „int” w deklaracji, chociaż zdecydowanie warto to napisać. C zawsze domyślnie przyjmuje typy „int”. Spróbuj!
ephemient
"z zakresem pliku lokalnego o wartości 42" ?? czy jest to dla całej jednostki kompilacji?
aniliitb10
4

W C ++

static const int foo = 42;

jest preferowanym sposobem definiowania i używania stałych. Używam raczej tego niż

#define foo 42

ponieważ nie podważa systemu bezpieczeństwa typu.

paxos1977
źródło
4

Do wszystkich świetnych odpowiedzi chcę dodać mały szczegół:

Jeśli piszesz wtyczki (np. Biblioteki DLL lub .so, które mają być ładowane przez system CAD), statyczność jest ratunkiem, który pozwala uniknąć kolizji nazw, takich jak ta:

  1. System CAD ładuje wtyczkę A, która ma „const int foo = 42;” w tym.
  2. System ładuje wtyczkę B, która ma "const int foo = 23;" w tym.
  3. W rezultacie wtyczka B użyje wartości 42 dla foo, ponieważ program ładujący wtyczki zda sobie sprawę, że istnieje już "foo" z zewnętrznym łączem.

Co gorsza: krok 3 może zachowywać się inaczej w zależności od optymalizacji kompilatora, mechanizmu ładowania wtyczki itp.

Miałem ten problem raz z dwiema funkcjami pomocniczymi (ta sama nazwa, inne zachowanie) w dwóch wtyczkach. Zadeklarowanie ich jako statyczne rozwiązało problem.

czarny
źródło
Coś wydawało się dziwne w kolizjach nazw między dwiema wtyczkami, co skłoniło mnie do zbadania mapy łączy dla jednej z wielu moich bibliotek DLL, która definiuje m_hDfltHeap jako uchwyt z łączeniem zewnętrznym. Rzeczywiście, cały świat może go zobaczyć i używać, wymieniony na mapie powiązań jako _m_hDfltHeap. Całkiem zapomniałem o tym fakcie.
David A. Gray,
4

Zgodnie ze specyfikacją C99 / GNU99:

  • static

    • jest specyfikatorem klasy pamięci

    • obiekty o zasięgu na poziomie pliku domyślnie mają połączenie zewnętrzne

    • obiekty o zasięgu na poziomie pliku ze statycznym specyfikatorem mają wewnętrzne powiązanie
  • const

    • jest kwalifikatorem typu (jest częścią typu)

    • słowo kluczowe stosowane do bezpośredniego lewego wystąpienia - tj

      • MyObj const * myVar; - niekwalifikowany wskaźnik do stałego kwalifikowanego typu obiektu

      • MyObj * const myVar; - const kwalifikowany wskaźnik do niekwalifikowanego typu obiektu

    • Użycie skrajnie lewe - stosowane do typu obiektu, a nie do zmiennej

      • const MyObj * myVar; - niekwalifikowany wskaźnik do stałego kwalifikowanego typu obiektu

A ZATEM:

static NSString * const myVar; - stały wskaźnik do niezmiennego łańcucha z wewnętrznym połączeniem.

Brak staticsłowa kluczowego spowoduje, że nazwa zmiennej stanie się globalna i może prowadzić do konfliktów nazw w aplikacji.

Alexey Pelekh
źródło
4

inlineZmienne C ++ 17

Jeśli wpisałeś w Google „C ++ const static”, jest bardzo prawdopodobne, że naprawdę chcesz użyć zmiennych wbudowanych w C ++ 17 .

Ta niesamowita funkcja C ++ 17 pozwala nam:

  • wygodnie używać tylko jednego adresu pamięci dla każdej stałej
  • zapisz to jako constexpr: Jak zadeklarować extern constexpr?
  • zrób to w jednej linii z jednego nagłówka

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

Skompiluj i uruchom:

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 upstream .

Zobacz także: Jak działają zmienne wbudowane?

Standard C ++ dotyczący zmiennych wbudowanych

Standard C ++ gwarantuje, że adresy będą takie same. C ++ 17 N4659 standardowa wersja robocza 10.1.6 „Specyfikator wbudowany”:

6 Funkcja lub zmienna wbudowana z połączeniem zewnętrznym ma ten sam adres we wszystkich jednostkach tłumaczeniowych.

cppreference https://en.cppreference.com/w/cpp/language/inline wyjaśnia, że ​​jeśli staticnie jest podane, ma link zewnętrzny.

Implementacja zmiennych inline GCC

Możemy obserwować, jak jest realizowany za pomocą:

nm main.o notmain.o

który zawiera:

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

i man nmmówi o u:

„u” Symbol jest unikalnym symbolem globalnym. To jest rozszerzenie GNU do standardowego zestawu powiązań symboli ELF. Dla takiego symbolu dynamiczny linker upewni się, że w całym procesie jest tylko jeden symbol o tej nazwie i typie w użyciu.

więc widzimy, że jest do tego dedykowane rozszerzenie ELF.

Przed C ++ 17: extern const

Przed C ++ 17, a także w C, możemy osiągnąć bardzo podobny efekt za pomocą extern const, co doprowadzi do użycia jednej lokalizacji pamięci.

Wady inlineto:

  • nie jest możliwe utworzenie zmiennej constexprtą techniką, inlinepozwala tylko na to: Jak zadeklarować constexpr extern?
  • jest mniej elegancki, ponieważ musisz zadeklarować i zdefiniować zmienną oddzielnie w nagłówku i pliku cpp

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 upstream .

Alternatywy tylko dla nagłówków przed C ++ 17

Nie są one tak dobre, jak externrozwiązanie, ale działają i zajmują tylko jedną lokalizację w pamięci:

constexprFunkcja, ponieważ constexprzakładainline i inline pozwala (siły) definicja pojawiać się na każdej jednostki tłumaczeniowej :

constexpr int shared_inline_constexpr() { return 42; }

i założę się, że każdy przyzwoity kompilator wbuduje wywołanie.

Możesz również użyć zmiennej statycznej constlub, constexprjak w:

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

ale nie możesz zrobić takich rzeczy, jak pobranie jego adresu, w przeciwnym razie zostanie on użyty odr, zobacz także: Definiowanie stałych elementów danych constexpr

do

W C sytuacja jest taka sama, jak w C ++ przed C ++ 17, przesłałem przykład pod adresem: Co oznacza „statyczny” w C?

Jedyną różnicą jest to, że w C ++ constimplikuje to staticdla globals, ale nie w C: C ++ semantyka `static const` vs` const`

Jakikolwiek sposób, aby w pełni go wbudować?

DO ZROBIENIA: czy istnieje sposób na pełne wstawienie zmiennej bez użycia jakiejkolwiek pamięci?

Podobnie jak robi to preprocesor.

Wymagałoby to w jakiś sposób:

  • zakazanie lub wykrywanie, czy adres zmiennej jest brany
  • dodaj te informacje do plików obiektowych ELF i pozwól LTO zoptymalizować je

Związane z:

Testowany w Ubuntu 18.10, GCC 8.2.0.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
2

Tak, ukrywa zmienną w module przed innymi modułami. W C ++ używam go, gdy nie chcę / nie muszę zmieniać pliku .h, co spowoduje niepotrzebną przebudowę innych plików. Ponadto stawiam najpierw statyczny:

static const int foo = 42;

Ponadto, w zależności od jego użycia, kompilator nawet nie przydzieli dla niego pamięci i po prostu „wstawi” wartość, w której jest używany. Bez statycznego kompilator nie może założyć, że nie jest używany w innym miejscu i nie może być wbudowany.

Jim Buck
źródło
2

Jest to między innymi stała globalna widoczna / dostępna tylko w module kompilacji (plik .cpp). BTW używanie do tego celu statycznego jest przestarzałe. Lepiej użyj anonimowej przestrzeni nazw i wyliczenia:

namespace
{
  enum
  {
     foo = 42
  };
}
Roskoto
źródło
zmusiłoby to kompilator do nie traktowania foo jako stałej i jako takie utrudnia optymalizację.
Nils Pipenbrinck
wartości wyliczeń są zawsze stałe, więc nie widzę, jak to utrudni jakiekolwiek optymalizacje
Roskoto,
ah - prawda .. mój błąd. pomyślałem, że użyłeś prostej zmiennej int.
Nils Pipenbrinck
Roskoto, nie jestem pewien, jakie korzyści przynosi to enumw tym kontekście. Możesz rozwinąć temat? Takie enumssą zwykle używane tylko po to, aby zapobiec przydzielaniu przez kompilator jakiejkolwiek przestrzeni na wartość (chociaż współczesne kompilatory nie potrzebują do tego tego enumhackowania) i aby zapobiec tworzeniu wskaźników do wartości.
Konrad Rudolph
Konrad, Jaki dokładnie problem widzisz w używaniu wyliczenia w tym przypadku? Wyliczenia są używane, gdy potrzebujesz stałych liczb całkowitych, co jest dokładnie tym przypadkiem.
Roskoto,
1

Ustawienie go jako prywatnego nadal oznaczałoby, że pojawia się w nagłówku. Zwykle używam „najsłabszego” sposobu, który działa. Zobacz ten klasyczny artykuł Scotta Meyersa: http://www.ddj.com/cpp/184401197 (dotyczy funkcji, ale można go również zastosować tutaj).

yrp
źródło