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ązanie zewnętrzne 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ć a 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() // this function has external linkage, and can be seen by other files
{
    std::cout << "Hi!\n";
}

main.cpp:

void sayHi(); // forward declaration for function sayHi, makes sayHi accessible in this file

int main()
{
    sayHi(); // call to function defined in another file, linker will connect this call to the function definition

    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 extern słowo kluczowe, aby to zrobić:

int g_x { 2 }; // non-constant globals are external by default (no need to use extern)

extern const int g_y { 3 }; // const globals can be defined as extern, making them external
extern constexpr int g_z { 3 }; // constexpr globals can be defined as extern, making them external (but this is pretty useless, see the warning in the next section)

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ć a 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 extern słowo kluczowe (bez wartości inicjalizacyjnej).

Oto przykład użycia zmiennych deklaracji forward:

main.cpp:

#include <iostream>

extern int g_x;       // this extern is a forward declaration of a variable named g_x that is defined somewhere else
extern const int g_y; // this extern is a forward declaration of a const variable named g_y that is defined somewhere else

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

    return 0;
}

Oto definicje tych zmiennych:

a.cpp:

// global variable definitions
int g_x { 2 };              // non-constant globals have external linkage by default
extern const int g_y { 3 }; // this extern gives g_y external linkage

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 extern słowo kluczowe 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 extern słowo kluczowe, 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ą extern słowa kluczowego — 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ą extern słowa kluczowego, 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 };        // extern by default
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;                 // forward declaration for non-constant global variable
extern const int g_y;           // forward declaration for const global variable
extern constexpr int g_y;       // not allowed: constexpr variables can't be forward declared

// External global variable definitions (no extern)
int g_x;                        // defines non-initialized external global variable (zero initialized by default)
int g_x { 1 };                  // defines initialized external global variable

// External const global variable definitions (extern w/ initializer)
extern const int g_x { 2 };     // defines initialized const external global variable
extern constexpr int g_x { 3 }; // defines initialized constexpr external global variable

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