W lekcji 7.3 -- Zmienne lokalne powiedzieliśmy: „Powiązanie identyfikatora określa, czy inne deklaracje tej nazwy odnoszą się do tego samego obiektu, czy nie” i omówiliśmy, w jaki sposób zmienne lokalne mają no linkage.
Zmienne globalne, a identyfikatory funkcji mogą mieć internal linkage lub external linkage. W tej lekcji omówimy przypadek powiązania wewnętrznego, a przypadek powiązania zewnętrznego na lekcji 7.7 -- Powiązania zewnętrzne i deklaracje zmiennych forward.
Identyfikator z powiązanie wewnętrzne można zobaczyć i wykorzystać w ramach jednej jednostki tłumaczeniowej, ale nie jest on dostępny z innych jednostek tłumaczeniowych. Oznacza to, że jeśli dwa pliki źródłowe mają identycznie nazwane identyfikatory z wewnętrznym powiązaniem, identyfikatory te będą traktowane jako niezależne (i nie będą skutkować naruszeniem ODR z powodu duplikacji definicji).
Kluczowa informacja
Identyfikatory z wewnętrznym powiązaniem mogą w ogóle nie być widoczne dla linkera. Alternatywnie mogą być widoczne dla linkera, ale oznaczone do użycia tylko w określonej jednostce tłumaczeniowej.
Powiązana treść
Jednostki tłumaczeniowe omówimy na lekcji 2.10 — Wprowadzenie do preprocesora.
Zmienne globalne z wewnętrznym powiązaniem
Zmienne globalne z wewnętrznym powiązaniem są czasami nazywane zmiennymi wewnętrznymi.
Aby niestała zmienna globalna była wewnętrzna, używamy static słowo kluczowe.
#include <iostream>
static int g_x{}; // non-constant globals have external linkage by default, but can be given internal linkage via the static keyword
const int g_y{ 1 }; // const globals have internal linkage by default
constexpr int g_z{ 2 }; // constexpr globals have internal linkage by default
int main()
{
std::cout << g_x << ' ' << g_y << ' ' << g_z << '\n';
return 0;
}Zmienne globalne Const i constexpr mają domyślnie wewnętrzne powiązanie (i dlatego nie potrzebują static słowa kluczowego — jeśli zostanie użyte, zostanie zignorowane).
Oto przykład wielu plików wykorzystujących zmienne wewnętrzne:
a.cpp:
[[maybe_unused]] constexpr int g_x { 2 }; // this internal g_x is only accessible within a.cppmain.cpp:
#include <iostream>
static int g_x { 3 }; // this separate internal g_x is only accessible within main.cpp
int main()
{
std::cout << g_x << '\n'; // uses main.cpp's g_x, prints 3
return 0;
}Ten program wypisuje:
3
Ponieważ g_x jest wewnętrzne dla każdego pliku, main.cpp nie ma pojęcia, że a.cpp ma również zmienna o nazwie g_x (i odwrotnie).
Dla zaawansowanych czytelników
Użycie static słowa kluczowego powyżej jest przykładem specyfikatora klasy pamięci, który ustawia zarówno powiązanie nazwy, jak i czas jej przechowywania. Najczęściej używany storage class specifiers są static, extern, I mutable. Termin storage class specifier jest najczęściej używany w dokumentacji technicznej.
Dla zaawansowanych czytelników
Standard C++11 (dodatek C) wyjaśnia, dlaczego zmienne const mają domyślnie wewnętrzne powiązania: „Ponieważ obiekty const mogą być używane jako wartości w czasie kompilacji w C++, ta funkcja nakłania programistów do zapewnienia jawnych wartości inicjalizujących dla każdej stałej. Ta funkcja pozwala użytkownikowi na umieszczanie obiektów const w plikach nagłówkowych, które są zawarte w wielu kompilacjach jednostek.”
Projektantom C++ zamierzono dwie rzeczy:
- Obiekty Const powinny nadawać się do stosowania w wyrażeniach stałych. Aby można było ich używać w wyrażeniach stałych, kompilator musi widzieć definicję (a nie deklarację), aby została oceniona w czasie kompilacji.
- Obiekty const powinny mieć możliwość propagowania poprzez pliki nagłówkowe.
Obiekty z zewnętrznym połączeniem można definiować tylko w pojedynczej jednostce translacyjnej bez naruszania ODR - inne jednostki translacyjne muszą uzyskać dostęp do tych obiektów poprzez deklarację forward. Jeżeli obiekty const miałyby domyślnie połączenie zewnętrzne, można by ich używać tylko w wyrażeniach stałych w pojedynczej jednostce tłumaczenia zawierającej definicję i nie można by ich skutecznie propagować za pomocą plików nagłówkowych, ponieważ #włączenie nagłówka do więcej niż jednego pliku źródłowego skutkowałoby naruszeniem ODR.
Obiekty z wewnętrznym powiązaniem mogą mieć definicję w każdej jednostce tłumaczenia, gdzie są potrzebne, bez naruszania ODR. Pozwala to na umieszczenie obiektów const w pliku nagłówkowym i #włączenie do dowolnej liczby jednostek tłumaczeniowych bez naruszania ODR. A ponieważ każda jednostka translacyjna ma raczej definicję niż deklarację, gwarantuje to, że te stałe mogą być używane w wyrażeniach stałych w obrębie jednostki translacyjnej.
Zmienne wbudowane (które mogą mieć zewnętrzne powiązania bez powodowania naruszeń ODR) zostały wprowadzone dopiero w C++ 17.
Funkcje z wewnętrznym powiązaniem
Jak zauważono powyżej, identyfikatory funkcji również mają powiązania. Funkcje domyślnie korzystają z powiązań zewnętrznych (co omówimy w następnej lekcji), ale można je ustawić na powiązania wewnętrzne za pomocą static słowem kluczowym:
add.cpp:
// This function is declared as static, and can now be used only within this file
// Attempts to access it from another file via a function forward declaration will fail
[[maybe_unused]] static int add(int x, int y)
{
return x + y;
}main.cpp:
#include <iostream>
int add(int x, int y); // forward declaration for function add
int main()
{
std::cout << add(3, 4) << '\n';
return 0;
}Ten program nie będzie łączył, ponieważ funkcja add nie jest dostępna poza add.cpp.
Regułą jednej definicji i powiązaniem wewnętrznym
W lekcji 2.7 -- Deklaracje przesyłania dalej i definicje. Zauważyliśmy, że reguła jednej definicji mówi, że obiekt lub funkcja nie może mieć więcej niż jednej definicji, ani w pliku, ani w pliku program.
Warto jednak zaznaczyć, że obiekty wewnętrzne (i funkcje) zdefiniowane w różnych plikach uznawane są za niezależne byty (nawet jeśli ich nazwy i typy są identyczne), zatem nie ma tu mowy o naruszeniu zasady jednej definicji. Każdy obiekt wewnętrzny ma tylko jedną definicję.
static w porównaniu z nienazwanymi przestrzeniami nazw
We współczesnym C++ użycie słowa kluczowego static do podawania wewnętrznych powiązań identyfikatorów wypada z łask. Nienazwane przestrzenie nazw mogą dawać wewnętrzne powiązania z szerszym zakresem identyfikatorów (np. identyfikatorami typów) i lepiej nadają się do nadawania wewnętrznych powiązań wielu identyfikatorom.
Nienazwane przestrzenie nazw omówimy w lekcji >7.14 -- Nienazwane i wbudowane przestrzenie nazw.
Po co zawracać sobie głowę dawaniem wewnętrznych powiązań identyfikatorów?
Zazwyczaj są dwa powody, aby je podać identyfikatory wewnętrzne powiązanie:
- Istnieje identyfikator, który chcemy mieć pewność, że nie będzie dostępny dla innych plików. Może to być zmienna globalna, z którą nie chcemy się bawić, lub funkcja pomocnicza, której nie chcemy wywoływać.
- Być pedantycznym w unikaniu kolizji nazewnictwa. Ponieważ identyfikatory z wewnętrznym powiązaniem nie są widoczne dla linkera, mogą kolidować jedynie z nazwami w tej samej jednostce tłumaczenia, a nie w całym programie.
Wiele nowoczesnych przewodników programistycznych zaleca podawanie każdej zmiennej i funkcji, która nie jest przeznaczona do użycia, z innego wewnętrznego powiązania pliku. Jeśli masz dyscyplinę, jest to dobra rekomendacja.
Na razie zalecamy co najmniej łagodniejsze podejście: podaj wewnętrzne powiązanie z dowolnym identyfikatorem, dla którego masz wyraźny powód, aby uniemożliwić dostęp z innych plików.
Najlepsza praktyka
Nadaj wewnętrzne powiązanie identyfikatorom, gdy masz wyraźny powód, aby zabronić dostępu z innych plików.
Rozważ nadanie wszystkich identyfikatorów, których nie chcesz, aby były dostępne dla innych plików. (użyj w tym celu nienazwanej przestrzeni nazw).
Szybkie podsumowanie
// Internal global variables definitions:
static int g_x; // defines non-initialized internal global variable (zero initialized by default)
static int g_x{ 1 }; // defines initialized internal global variable
const int g_y { 2 }; // defines initialized internal global const variable
constexpr int g_y { 3 }; // defines initialized internal global constexpr variable
// Internal function definitions:
static int foo() {}; // defines internal functionObszerne podsumowanie przedstawiamy w lekcji 7.12 — Podsumowanie zakresu, czasu trwania i powiązania.

