Linkage
Abbiamo gia` visto che ad ogni identificatore e` associato uno scope e una
lifetime, ma gli identificatori di variabili, costanti e funzioni possiedono
anche un linkage.
Per comprendere meglio il concetto e` necessario sapere che in C e in C++ l'unita`
di compilazione e` il file, un programma puo` consistere di piu` file che vengono
compilati separatamente e poi linkati (collegati) per ottenere un file eseguibile.
Quest'ultima operazione e` svolta dal linker e possiamo pensare al concetto
di linkage sostanzialmente come a una sorta di scope dal punto di vista del
linker. Facciamo un esempio:
// File a.cpp
int a = 5;
// File b.cpp
extern int a;
int GetVar() {
return a;
}
Il primo file dichiara una variabile intera e la inizializza, il secondo (trascuriamone
per ora la prima riga di codice) dichiara una funzione che ne restituisce il
valore. La compilazione del primo file non e` un problema, ma nel secondo file
GetVar() deve utilizzare un nome dichiarato in un altro file; perche`
la cosa sia possibile bisogna informare il compilatore che tale nome e` dichiarato
da qualche altra parte e che il riferimento a tale nome non puo` essere risolto
se non quando tutti i file sono stati compilati, solo il linker quindi puo`
risolvere il problema collegando insieme i due file. Il compilatore deve dunque
essere informato dell'esistenza della variabile al fine di non generare un messaggio
di errore; tale operazione viene effettuata tramite la keyword extern .
In effetti la riga extern int a; non dichiara un nuovo identificatore,
ma dice "La variabile intera a e` dichiarata da qualche altra
parte, lascia solo lo spazio per risolvere il riferimento". Se la keyword
extern fosse stata omessa il compilatore avrebbe interpretato la
riga come una nuova dichiarazione e avrebbe risolto il riferimento in GetVar()
in favore di tale definizione; in fase di linking comunque si sarebbe verificato
un errore perche` a sarebbe stata definita due volte (una per file),
il perche` di tale errore sara` chiaro piu` avanti.
Naturalmente extern si puo` usare anche con le funzioni (anche
se come vedremo e` ridondante):
// File a.cpp
int a = 5;
int f(int c) {
return a+c;
}
// File b.cpp
extern int f(int);
int GetVar() {
return f(5);
}
Si noti che e` necessario che extern sia seguita dal prototipo
completo della funzione, al fine di consentire al compilatore di generare codice
corretto e di eseguire i controlli di tipo sui parametri e il valore restituito.
Come gia` detto, il C++ ha un'alta compatibilita` col C, tant'e` che e` possibile
interfacciare codice C++ con codice C; anche in questo caso l'aiuto ci viene
dalla keyword extern . Per poter linkare un modulo C con un modulo
C++ e` necessario indicare al compilatore le nostre intenzioni:
// Contenuto file C++
extern "C" int CFunc(char*);
extern "C" char* CFunc2(int);
// oppure per risparmiare tempo
extern "C" {
void CFunc1(void);
int* CFunc2(int, char);
char* strcpy(char*, const char*);
}
La presenza di "C" serve a indicare che bisogna adottare
le convenzioni del C sulla codifica dei nomi (in quanto il compilatore C++ codifica
internamente i nomi degli identificatori in modo assai diverso).
Un altro uso di extern e` quello di ritardare la definizione di
una variabile o di una funzione all'interno dello stesso file, ad esempio per
realizzare funzioni mutuamente ricorsive:
extern int Func2(int);
int Func1(int c) {
if (c==0) return 1;
return Func2(c-1);
}
int Func2(int c) {
if (c==0) return 2;
return Func1(c-1);
}
Tuttavia nel caso delle funzioni non e` necessario l'uso di extern ,
il solo prototipo e` sufficiente, e` invece necessario ad esempio per le variabili:
int Func2(int); // extern non necessaria
extern int a; // extern necessaria
int Func1(int c) {
if (c==0) return 1;
return Func2(c-1);
}
int Func2(int c) {
if (c==0) return a;
return Func1(c-1);
}
int a = 10; // definisce la variabile
//
precedentemente dichiarata
I nomi che sono visibili all'esterno di un file sono detti avere linkage
esterno; tutte le variabili globali hanno linkage esterno, cosi` come
le funzioni globali non inline; le funzioni inline, tutte le costanti e le dichiarazioni
fatte in un blocco hanno invece linkage interno (cioe` non sono visibili all'esterno
del file); i nomi di tipo non hanno alcun linkage, ma devono riferire ad una
unica definizione:
// File 1.cpp
enum Color { Red, Green, Blue };
extern void f(Color);
// File2.cpp
enum Color { Red, Green, Blue };
void f(Color c) { /* ... */ }
Una situazione di questo tipo e` illecita, ma molti compilatori potrebbero
non accorgersi dell'errore.
Per quanto concerne i nomi di tipo, fanno eccezione quelli definiti tramite
typedef in quanto non sono veri tipi, ma solo abbreviazioni.
E` possibile forzare un identificatore globale ad avere linkage interno utilizzando
la keyword static :
// File a.cpp
static int a = 5; // linkage interno
int f(int c) { // linkage
esterno
return a+c;
}
// File b.cpp
extern int f(int);
static int GetVar() { // linkage interno
return f(5);
}
Si faccia attenzione al significato di static : nel caso di variabili
locali static serve a modificarne la lifetime (durata), nel caso
di nomi globali invece modifica il linkage.
L'importanza di poter restringere il linkage e` ovvia; supponete di voler realizzare
una libreria di funzioni, alcune serviranno solo a scopi interni alla libreria
e non serve (anzi e` pericoloso) esportarle, per fare cio` basta dichiarare
static i nomi globali che volete incapsulare.
|