Quando si crea una libreria di classi in C ++, è possibile scegliere tra librerie dinamiche ( .dll
, .so
) e statiche ( .lib
, .a
). Qual è la differenza tra loro e quando è appropriato usare quale?
Quando si crea una libreria di classi in C ++, è possibile scegliere tra librerie dinamiche ( .dll
, .so
) e statiche ( .lib
, .a
). Qual è la differenza tra loro e quando è appropriato usare quale?
Risposte:
Le librerie statiche aumentano la dimensione del codice nel tuo binario. Vengono sempre caricati e qualsiasi versione del codice compilato è la versione del codice che verrà eseguita.
Le librerie dinamiche vengono archiviate e versionate separatamente. È possibile caricare una versione della libreria dinamica che non era quella originale fornita con il codice se l'aggiornamento è considerato binario compatibile con la versione originale.
Inoltre le librerie dinamiche non sono necessariamente caricate - di solito vengono caricate quando vengono chiamate per la prima volta - e possono essere condivise tra i componenti che utilizzano la stessa libreria (più caricamenti di dati, un caricamento di codice).
Le librerie dinamiche erano considerate l'approccio migliore per la maggior parte del tempo, ma in origine avevano un grosso difetto (inferno DLL di Google), che è stato quasi completamente eliminato dai più recenti sistemi operativi Windows (in particolare Windows XP).
Altri hanno spiegato adeguatamente cos'è una libreria statica, ma vorrei sottolineare alcune delle avvertenze sull'uso delle librerie statiche, almeno su Windows:
Singletons: se qualcosa deve essere globale / statico e unico, fai molta attenzione a metterlo in una libreria statica. Se più DLL sono collegate a quella libreria statica, ognuna otterrà la propria copia del singleton. Tuttavia, se l'applicazione è un singolo EXE senza DLL personalizzate, questo potrebbe non essere un problema.
Rimozione di codice senza riferimento: quando si effettua il collegamento a una libreria statica, solo le parti della libreria statica a cui fa riferimento la DLL / EXE verranno collegate alla DLL / EXE.
Per esempio, se mylib.lib
contiene a.obj
e b.obj
e la DLL / EXE solo riferimenti funzioni o variabili dal a.obj
, la totalità della b.obj
andranno scartato dal linker. Se b.obj
contiene oggetti globali / statici, i loro costruttori e distruttori non verranno eseguiti. Se quei costruttori / distruttori hanno effetti collaterali, potresti essere deluso dalla loro assenza.
Allo stesso modo, se la libreria statica contiene punti speciali speciali, potrebbe essere necessario assicurarsi che siano effettivamente inclusi. Un esempio di ciò nella programmazione integrata (ok, non in Windows) potrebbe essere un gestore di interrupt contrassegnato come associato a un indirizzo specifico. È inoltre necessario contrassegnare il gestore di interrupt come un punto di accesso per assicurarsi che non venga scartato.
Un'altra conseguenza di ciò è che una libreria statica può contenere file oggetto che sono completamente inutilizzabili a causa di riferimenti non risolti, ma non causerà un errore del linker fino a quando non si fa riferimento a una funzione o variabile da tali file oggetto. Ciò può accadere molto tempo dopo la scrittura della libreria.
Simboli di debug: è possibile che si desideri un PDB separato per ciascuna libreria statica oppure si consiglia di posizionare i simboli di debug nei file oggetto in modo che vengano inseriti nel PDB per la DLL / EXE. La documentazione di Visual C ++ spiega le opzioni necessarie .
RTTI: potresti finire con più type_info
oggetti per la stessa classe se colleghi una singola libreria statica in più DLL. Se il tuo programma presume che type_info
siano dati "singleton" e utilizzi &typeid()
o type_info::before()
, potresti ottenere risultati indesiderati e sorprendenti.
Una lib è un'unità di codice inclusa nel file eseguibile dell'applicazione.
Una dll è un'unità autonoma di codice eseguibile. Viene caricato nel processo solo quando viene effettuata una chiamata in quel codice. Una dll può essere utilizzata da più applicazioni e caricata in più processi, pur mantenendo una sola copia del codice sul disco rigido.
Professionisti Dll : possono essere utilizzati per riutilizzare / condividere il codice tra diversi prodotti; caricare nella memoria di processo su richiesta e può essere scaricato quando non necessario; può essere aggiornato indipendentemente dal resto del programma.
Contro dll : impatto delle prestazioni del caricamento dll e del rebasing del codice; problemi di versioning ("dll hell")
Pro Lib : nessun impatto sulle prestazioni in quanto il codice viene sempre caricato nel processo e non viene riformulato; nessun problema di versioning.
Contro : eseguibile / processo "bloat" - tutto il codice è nell'eseguibile e viene caricato all'avvio del processo; nessun riutilizzo / condivisione: ogni prodotto ha la propria copia del codice.
Oltre alle implicazioni tecniche delle librerie statiche e dinamiche (i file statici raggruppano tutto in una grande libreria binaria rispetto alle librerie dinamiche che consentono la condivisione del codice tra diversi eseguibili), ci sono implicazioni legali .
Ad esempio, se si utilizza il codice con licenza LGPL e si collega staticamente a una libreria LGPL (e quindi si crea un grosso binario), il codice diventa automaticamente codice LGPL di origine aperta ( gratuito come in libertà) . Se si collega a oggetti condivisi, è necessario solo LGPL i miglioramenti / correzioni di errori apportati alla libreria LGPL stessa.
Questo diventa un problema molto più importante se stai decidendo come compilare le tue applicazioni mobili, ad esempio (in Android hai una scelta tra statica e dinamica, in iOS non lo fai - è sempre statica).
I programmi C ++ sono costruiti in due fasi
La libreria statica (.lib) è solo un pacchetto di file .obj e quindi non è un programma completo. Non ha subito la seconda fase (collegamento) di costruzione di un programma. Le DLL, d'altra parte, sono come exe e quindi sono programmi completi.
Se costruisci una libreria statica, questa non è ancora collegata e quindi i consumatori della tua libreria statica dovranno usare lo stesso compilatore che hai usato (se hai usato g ++, dovranno usare g ++).
Se invece hai creato una dll (e l'hai costruita correttamente ), hai creato un programma completo che tutti i consumatori possono usare, indipendentemente dal compilatore che stanno usando. Esistono tuttavia alcune restrizioni sull'esportazione da una dll, se si desidera la compatibilità tra compilatori.
consumers of your static library will have to use the same compiler that you used
se la libreria statica utilizza una libreria C ++, ad esempio #include <iostream>
.
$$:~/static [32]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/static [33]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H
void foo();
#endif
$$:~/static [34]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/static [35]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H
void foo2();
#endif
$$:~/static [36]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/static [37]> cat makefile
hello: hello.o libtest.a
cc -o hello hello.o -L. -ltest
hello.o: hello.c
cc -c hello.c -I`pwd`
libtest.a:foo.o foo2.o
ar cr libtest.a foo.o foo2.o
foo.o:foo.c
cc -c foo.c
foo2.o:foo.c
cc -c foo2.c
clean:
rm -f foo.o foo2.o libtest.a hello.o
$$:~/static [38]>
$$:~/dynamic [44]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/dynamic [45]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H
void foo();
#endif
$$:~/dynamic [46]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/dynamic [47]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H
void foo2();
#endif
$$:~/dynamic [48]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/dynamic [49]> cat makefile
hello:hello.o libtest.sl
cc -o hello hello.o -L`pwd` -ltest
hello.o:
cc -c -b hello.c -I`pwd`
libtest.sl:foo.o foo2.o
cc -G -b -o libtest.sl foo.o foo2.o
foo.o:foo.c
cc -c -b foo.c
foo2.o:foo.c
cc -c -b foo2.c
clean:
rm -f libtest.sl foo.o foo
2.o hello.o
$$:~/dynamic [50]>
Una libreria statica viene compilata nel client. Un file .lib viene utilizzato in fase di compilazione e il contenuto della libreria diventa parte dell'eseguibile di consumo.
Una libreria dinamica viene caricata in fase di runtime e non compilata nell'eseguibile client. Le librerie dinamiche sono più flessibili poiché più eseguibili client possono caricare una DLL e utilizzarne le funzionalità. Ciò consente inoltre di ridurre al minimo le dimensioni e la manutenibilità complessive del codice client.
Dovresti riflettere attentamente sui cambiamenti nel tempo, sul controllo delle versioni, sulla stabilità, sulla compatibilità, ecc.
Se ci sono due app che usano il codice condiviso, vuoi forzare queste app a cambiare insieme, nel caso in cui debbano essere compatibili tra loro? Quindi utilizzare la dll. Tutti gli exe useranno lo stesso codice.
Oppure vuoi isolarli l'uno dall'altro, in modo da poterne cambiare uno e avere la certezza di non aver rotto l'altro. Quindi utilizzare la lib statica.
L'inferno DLL è quando probabilmente DOVREBBE AVERE usato una lib statica, ma invece hai usato una dll, e non tutti gli ex sono compatibili.
Una libreria statica deve essere collegata all'eseguibile finale; diventa parte dell'eseguibile e lo segue ovunque vada. Una libreria dinamica viene caricata ogni volta che viene eseguito l'eseguibile e rimane separata dall'eseguibile come file DLL.
Si utilizzerà una DLL quando si desidera poter modificare la funzionalità fornita dalla libreria senza ricollegare l'eseguibile (basta sostituire il file DLL, senza sostituire il file eseguibile).
Utilizzeresti una libreria statica ogni volta che non hai un motivo per usare una libreria dinamica.
L'articolo di Ulrich Drepper su " Come scrivere librerie condivise " è anche una buona risorsa che descrive in dettaglio come sfruttare al meglio le librerie condivise o quelli che egli chiama "oggetti condivisi dinamici" (DSO). Si concentra maggiormente sulle librerie condivise nel formato binario ELF , ma alcune discussioni sono adatte anche per le DLL di Windows.
Per un'eccellente discussione su questo argomento, leggi questo articolo di Sun.
Comprende tutti i vantaggi tra cui la possibilità di inserire librerie di interposizione. Maggiori dettagli sull'interposizione sono disponibili in questo articolo qui .
In realtà il trade off che stai facendo (in un grande progetto) è nel tempo di caricamento iniziale, le librerie verranno collegate in un momento o nell'altro, la decisione che deve essere presa sarà il collegamento impiegherà abbastanza tempo di cui il compilatore ha bisogno per mordere il proiettile e farlo in anticipo, oppure il linker dinamico può farlo al momento del caricamento.
Se la tua libreria sarà condivisa tra diversi eseguibili, spesso ha senso renderla dinamica per ridurre le dimensioni degli eseguibili. Altrimenti, rendilo sicuramente statico.
Ci sono diversi svantaggi nell'uso di una dll. C'è un sovraccarico aggiuntivo per il caricamento e lo scaricamento. C'è anche una dipendenza aggiuntiva. Se modifichi la dll per renderla incompatibile con i tuoi execalbes, smetteranno di funzionare. D'altra parte, se si modifica una libreria statica, i file eseguibili compilati che utilizzano la versione precedente non saranno interessati.
Se la libreria è statica, al momento del collegamento il codice è collegato al tuo eseguibile. Questo rende il tuo eseguibile più grande (che se avessi seguito il percorso dinamico).
Se la libreria è dinamica, al momento del collegamento i riferimenti ai metodi richiesti sono integrati nell'eseguibile. Questo significa che devi spedire il tuo eseguibile e la libreria dinamica. Dovresti anche considerare se l'accesso condiviso al codice nella libreria è sicuro, preferisci l'indirizzo di caricamento tra le altre cose.
Se riesci a vivere con la libreria statica, vai con la libreria statica.
Usiamo molte DLL (> 100) nel nostro progetto. Queste DLL hanno dipendenze reciproche e quindi abbiamo scelto l'installazione del collegamento dinamico. Tuttavia presenta i seguenti svantaggi:
Forse una configurazione migliore è stata quella di rendere tutto una libreria statica (e quindi hai solo un eseguibile). Funziona solo se non si verifica alcuna duplicazione del codice. Un test sembra supportare questo presupposto, ma non sono riuscito a trovare un preventivo MSDN ufficiale. Quindi ad esempio crea 1 exe con:
Il codice e le variabili di shared_lib2 dovrebbero essere presenti nell'eseguibile unito finale solo una volta. Qualcuno può supportare questa domanda?
Le librerie statiche sono archivi che contengono il codice oggetto per la libreria, quando collegato a un'applicazione che il codice viene compilato nell'eseguibile. Le librerie condivise sono diverse in quanto non sono compilate nell'eseguibile. Invece il linker dinamico cerca alcune directory cercando le librerie necessarie, quindi le carica in memoria. Più di un eseguibile può utilizzare la stessa libreria condivisa contemporaneamente, riducendo l'utilizzo della memoria e le dimensioni dell'eseguibile. Tuttavia, ci sono quindi più file da distribuire con l'eseguibile. Devi assicurarti che la libreria sia installata sul sistema degli usi da qualche parte dove il linker può trovarla, il collegamento statico elimina questo problema ma si traduce in un file eseguibile più grande.
Se lavori su progetti incorporati o librerie statiche su piattaforme specializzate sono l'unica strada da percorrere, anche molte volte sono meno complicate da compilare nella tua applicazione. Anche avere progetti e makefile che includono tutto rende la vita più felice.
Darei una regola empirica generale che se si dispone di una base di codice di grandi dimensioni, il tutto costruito su librerie di livello inferiore (ad esempio un framework Utils o Gui), che si desidera suddividere in librerie più gestibili, quindi renderle librerie statiche. Le biblioteche dinamiche non ti comprano davvero nulla e ci sono meno sorprese, ad esempio ci sarà solo un'istanza di singoli.
Se si dispone di una libreria completamente separata dal resto della base di codice (ad esempio una libreria di terze parti), considerare di renderla una dll. Se la libreria è LGPL, potrebbe essere necessario utilizzare comunque una dll a causa delle condizioni di licenza.