7.7 — Powiązanie zewnętrzne i przekazywanie zmiennych deklaracje

W poprzedniej lekcji (7.6 – Powiązania wewnętrzne), rozmawialiśmy o tym, jak internal linkage ogranicza użycie identyfikatora do pojedynczego pliku. W tej lekcji omówimy koncepcję external linkage.

Identyfikator z powiązaniem zewnętrznym można zobaczyć i używać zarówno z pliku, w którym jest zdefiniowany, jak i z innych plików kodu (poprzez deklarację forward). W tym sensie identyfikatory z powiązaniem zewnętrznym są naprawdę „globalne”, ponieważ można ich używać w dowolnym miejscu programu!

Kluczowa informacja

Identyfikatory z powiązaniem zewnętrznym są widoczne dla linkera. Dzięki temu linker może zrobić dwie rzeczy:

  • Połącz identyfikator używany w jednej jednostce tłumaczeniowej z odpowiednią definicją w innej jednostce tłumaczeniowej.
  • Deduplikuj identyfikatory wbudowane, aby pozostała jedna definicja kanoniczna. Omawiamy zmienne i funkcje wbudowane na lekcji 7.9 -- Funkcje wbudowane i zmienne.

Funkcje mają domyślnie zewnętrzne powiązania

W lekcji 2.8 -- Programy z wieloma plikami kodu, dowiedziałeś się, że możesz wywołać funkcję zdefiniowaną w jednym pliku z innego pliku. Dzieje się tak dlatego, że funkcje mają domyślnie zewnętrzne powiązania.

Aby wywołać funkcję zdefiniowaną w innym pliku należy umieścić forward declaration dla tej funkcji w innych plikach, które chcą skorzystać z tej funkcji. Deklaracja forward informuje kompilator o istnieniu funkcji, a linker łączy wywołania funkcji z rzeczywistą definicją funkcji.

Oto przykład:

a.cpp:

#include <iostream>

void sayHi() // to funkcja ma zewnętrzne powiązanie i może być widoczna w innych plikach
{
    std::cout << "Hi!\n";
}

main.cpp:

void sayHi(); // deklaracja forward dla funkcji sayHi, udostępnia sayHi w tym plik

int main()
{
    sayHi(); // wywołanie funkcji zdefiniowanej w innym pliku, linker połączy to wywołanie z definicją funkcji

    return 0;
}

Powyższy program wypisuje:

Hi!

W powyższym przykładzie deklaracja funkcji w przód sayHi() w main.cpp pozwala main.cpp aby uzyskać dostęp do sayHi() funkcja zdefiniowana w a.cpp. Deklaracja forward spełnia wymagania kompilatora, a linker jest w stanie powiązać wywołanie funkcji z definicją funkcji.

Jeśli funkcja sayHi() zamiast tego miałby wewnętrzne połączenie, linker nie byłby w stanie połączyć wywołania funkcji z definicją funkcji, co spowodowałoby błąd linkera.

Zmienne globalne z powiązaniem zewnętrznym

Czasami nazywane są zmienne globalne z powiązaniami zewnętrznymi zmienne zewnętrzne. Aby uczynić zmienną globalną zewnętrzną (a tym samym dostępną dla innych plików), możemy użyć metody słowo kluczowe extern, aby to zrobić:

int g_x { 2 }; // niestałe globale są domyślnie zewnętrzne (nie ma potrzeby aby użyć extern)

extern const int g_y { 3 }; // const globale można zdefiniować jako extern, czyniąc je zewnętrznymi
extern constexpr int g_z { 3 }; // globale constexpr można zdefiniować jako zewnętrzne, czyniąc je zewnętrznymi (ale jest to całkiem bezużyteczne, zobacz ostrzeżenie w następnej sekcji)

int main()
{
    return 0;
}

Zmienne globalne inne niż stałe są domyślnie zewnętrzne, więc nie musimy ich oznaczać jako extern.

Zmienne deklaracje forward za pomocą słowa kluczowego extern

Aby faktycznie użyć zewnętrznej zmiennej globalnej, która została zdefiniowana w innym pliku, musisz także umieścić forward declaration dla zmiennej globalnej w innych plikach, które chcą użyć tej zmiennej. W przypadku zmiennych utworzenie deklaracji forward odbywa się również za pomocą metody słowo kluczowe extern (bez wartości inicjalizacyjnej).

Oto przykład użycia zmiennych deklaracji forward:

main.cpp:

#include <iostream>

extern int g_x;       // to extern jest deklaracją w przód zmiennej o nazwie g_x, która jest zdefiniowana gdzie indziej
extern const int g_y; // to extern jest deklaracją w przód zmiennej const o nazwie g_y, która jest zdefiniowana gdzie indziej

int main()
{
    std::cout << g_x << ' ' << g_y << '\n'; // prints 2 3

    return 0;
}

Oto definicje tych zmiennych:

a.cpp:

// definicje zmiennych globalnych
int g_x { 2 };              // niestałe globale mają domyślnie zewnętrzne powiązanie
extern const int g_y { 3 }; // to extern daje g_y zewnętrzne powiązanie

W powyższym przykładzie a.cpp i main.cpp oba odwołują się do tej samej zmiennej globalnej o nazwie g_x. Więc chociaż g_x jest zdefiniowany i zainicjowany w a.cpp, możemy wykorzystać jego wartość w main.cpp poprzez deklarację forward g_x.

Należy pamiętać, że słowo kluczowe extern ma różne znaczenia w różnych kontekstach. W niektórych kontekstach extern oznacza „daj tej zmiennej zewnętrzne powiązanie”. W innych kontekstach extern oznacza „jest to deklaracja forward dla zmiennej zewnętrznej, która jest zdefiniowana gdzie indziej”. Tak, jest to mylące, dlatego podsumowujemy wszystkie te zastosowania na lekcji 7.12 — Podsumowanie zakresu, czasu trwania i powiązania.

Ostrzeżenie

Jeśli chcesz zdefiniować niezainicjowaną zmienną globalną inną niż const, nie używaj słowa kluczowego extern, w przeciwnym razie C++ pomyśli, że próbujesz utworzyć deklarację forward dla zmiennej.

Ostrzeżenie

Chociaż zmiennym constexpr można nadać zewnętrzne powiązania poprzez słowo kluczowe extern, nie można ich zadeklarować w przód jako constexpr. Dzieje się tak, ponieważ kompilator musi znać wartość zmiennej constexpr (w czasie kompilacji). Jeśli ta wartość jest zdefiniowana w innym pliku, kompilator nie ma wglądu w to, jaka wartość została zdefiniowana w tym innym pliku.

Można jednak dalej zadeklarować zmienną constexpr jako stałą, którą kompilator potraktuje jako stałą czasu wykonania. Nie jest to szczególnie przydatne.

Zauważ, że deklaracje forward funkcji nie wymagają słowa kluczowego extern — kompilator jest w stanie stwierdzić, czy definiujesz nową funkcję, czy dokonujesz deklaracji forward na podstawie tego, czy podasz treść funkcji, czy nie. Deklaracje forward zmiennych do potrzebują słowa kluczowego extern, aby pomóc odróżnić definicje niezainicjowanych zmiennych od deklaracji zmiennych do przodu (poza tym wyglądają identycznie):

// non-constant 
int g_x;        // variable definition (no initializer)
int g_x { 1 };  // variable definition (w/ initializer)
extern int g_x; // forward declaration (no initializer)

// constant
extern const int g_y { 1 }; // variable definition (const requires initializers)
extern const int g_y;       // forward declaration (no initializer)

Unikaj używania extern na niestałej zmiennej globalnej z inicjatorem

Poniższe dwie linie są semantyczne odpowiednik:

int g_x { 1 };        // domyślnie zewnętrzny
extern int g_x { 1 }; // explicitly extern (may cause compiler warning)

Jednak Twój kompilator może wyświetlić ostrzeżenie dotyczące tego ostatniego stwierdzenia, nawet jeśli jest ono poprawne z technicznego punktu widzenia.

Pamiętaj, jak powiedzieliśmy, że kompilatory mają swobodę w wystawianiu diagnozy w przypadku rzeczy, które uznają za podejrzane? To jest jeden z takich przypadków. Konwencjonalnie extern jest stosowany do zmiennej innej niż stała, gdy chcemy deklaracji forward. Jednak dodanie inicjatora powoduje, że instrukcja staje się definicją. Kompilator informuje Cię, że coś jest nie tak. Aby to poprawić, usuń inicjator (jeśli miałeś na myśli deklarację forward) lub usuń extern (jeśli miałeś na myśli definicję).

Najlepsza praktyka

Używaj extern do deklaracji forward zmiennych globalnych lub definicji zmiennych globalnych const.
Nie używaj extern dla definicji zmiennych globalnych innych niż stałe (domyślnie są one extern).

szybkie podsumowanie

// Global variable forward declarations (extern w/ no initializer):
extern int g_y;                 // deklaracja forward dla niestałej zmiennej globalnej
extern const int g_y;           // deklaracja forward dla stałej zmiennej globalnej
extern constexpr int g_y;       // niedozwolone: zmiennych constexpr nie można zadeklarować w przód

// External global variable definitions (no extern)
int g_x;                        // definiuje niezainicjowaną zewnętrzną zmienną globalną (domyślnie inicjowane jest zero)
int g_x { 1 };                  // definiuje zainicjowaną zewnętrzną zmienną globalną

// External const global variable definitions (extern w/ initializer)
extern const int g_x { 2 };     // definiuje zainicjowaną stałą zewnętrzną zmienną globalną
extern constexpr int g_x { 3 }; // definiuje zainicjowaną zewnętrzną zmienną globalną constexpr

Obszerne podsumowanie przedstawiamy w lekcji 7.12 — Podsumowanie zakresu, czasu trwania i powiązania.

Czas quizu

Pytanie nr 1

Jaka jest różnica między zakresem, czasem trwania i powiązaniem zmiennej? Jaki rodzaj zakresu, czasu trwania i powiązania mają zmienne globalne?

Pokaż rozwiązanie

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
200 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze