C++ è un linguaggio compilato, il che significa che il codice che scrivi viene tradotto in codice macchina prima di essere eseguito. Sebbene la compilazione di un piccolo programma sia solitamente abbastanza indolore, le cose possono complicarsi quando il progetto cresce.
Librerie statiche e dinamiche
Quando compili un programma, puoi linkarlo a librerie. Ci sono due tipi di librerie: statiche e dinamiche.
Librerie statiche
Una libreria statica è un file che contiene del codice precompilato che può essere linkato a un programma durante la compilazione.
Il codice viene copiato nel programma e il tutto viene meso insieme in un singolo file eseguibile.
Per creare una libreria statica, puoi usare il comando ar
.
Questo genererà un archivio di file oggetto.
# Crea una libreria statica
ar rcs libmylib.a file1.o file2.o
Per elencare le librerie linkate staticamente ad un binario, puoi usare il comando nm
.
nm -D binary
Librerie dinamiche
Una libreria dinamica è un file che contiene codice compilato che può essere linkato a un programma a runtime. Il codice non appare nel binario, ma questo è a conoscenza dell’interfaccia della libreria e può invocarne le funzioni esposte.
Per creare una libreria dinamica, puoi usare il comando gcc
con l’opzione -shared
.
# Crea una libreria dinamica
gcc -shared -o libmylib.so file1.o file2.o
Per controllare quali librerie il binario andrà a cercare durante la sua esecuzione, puoi usare il comando ldd
.
# Controlla quali librerie sono linkate ad un binario
ldd binary
Su Windows, un’alternativa è usare l’utilità mingw-ldd, che può essere installata tramite pip. Dopo, basta eseguire il comando
mingw-ldd.exe binary --dll-lookup-dirs $env:GUROBI_HOME\bin 'C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x64
dove --dll-lookup-dirs
indica le cartelle in cui il software deve cercare le dll.
Metadati nei file Elf
RPATH
rpath indica il percorso di ricerca a runtime. È hard-coded nel file eseguibile o libreria. I loader di linking dinamico usano l’rpath per trovare le altre librerie richieste.
# Leggi l'rpath di una libreria
readelf -d bazel-bin/dlinear/libdlinear.so | grep 'R.*PATH'
SONAME
soname indica il nome della libreria condivisa. Viene solitamente considerato da altri binari per linkare la versione corretta della libreria condivisa a runtime.
# Leggi il SONAME
objdump -p libdlinear.so | grep SONAME
NEEDED
Quando un binario è linkato a delle librerie condivise, esse vengono contrassegnate come NEEDED nel binario per garantire che vengano caricate a runtime. Il campo NEEDED viene solitamente preso dal SONAME della libreria condivisa. Se il SONAME non è presente, il campo NEEDED sarà il percorso del file.
# Leggi il NEEDED
objdump -p libdlinear.so | grep NEEDED
Flag di compilazione
gcc
Flag generiche
-c
: Compila o assembla i file sorgente, ma non linka-o
: Posiziona l’output nel file specificato-L
: Aggiunge la directory alla lista delle directory da cercare per le librerie-l
: Linka la libreria
Flag del preprocessore
-I
: Aggiunge la directory alla lista delle directory da cercare per i file di intestazione-Iquote
: Aggiunge la directory alla lista delle directory da cercare per i file di intestazione, ma solo attraverso#include "file"
- Questo è utile quando si desidera includere un file di intestazione dalla directory corrente.
#include <file>
non funzionerebbe in questo caso
- Questo è utile quando si desidera includere un file di intestazione dalla directory corrente.
Flag del linker
-Wl,-rpath-link=.
: Aggiunge la directory corrente al percorso di ricerca a link-time-Wl,-rpath=.
: Aggiunge la directory corrente al percorso di ricerca a runtime-Wl,-gc-sections
: Rimuove il codice non utilizzato durante il linking statico, riducendo così la dimensione del binario-shared
: Crea una libreria condivisa-fPIC
: Genera codice indipendente dalla posizione- Questo è richiesto quando si creano librerie condivise
Flag di warning
-Wall
: Abilita tutti gli warning-Wextra
: Abilita warning extra-Werror
: Tratta gli warning come errori- Questo flag farà sì che il compilatore si arresti se vengono generati warning
Flag di ottimizzazione
-O0
: Disabilita l’ottimizzazione-O1
: Ottimizza per la dimensione-O2
: Ottimizza per la velocità-O3
: Ottimizza per la velocità e la dimensione
Flag di debugging
-g
: Genera informazioni di debugging
Flag di sicurezza
-U_FORTIFY_SOURCE
: Disabilita la funzione di fortificazione- Questa funzione viene utilizzata per rilevare alcuni errori di overflow del buffer quando si utilizzano le funzioni della libreria standard
-fstack-protector
: Abilita la protezione dello stack- Questa funzione aggiunge un valore canary allo stack per rilevare errori di overflow del buffer
Flag di dipendenza
-MD
: Genera informazioni di dipendenza-MMD
: Genera informazioni di dipendenza, ma non include gli header di sistema-MF <file>
: Scrive le informazioni di dipendenza nel file specificato
MSVC
Flag generiche
/c
: Compila senza linkare/Fe
: Specifica il nome del file di output/link
: Linka i file oggetto
Aggiungi intestazioni di sistema
/external:anglebrackets
: Tratta le intestazioni tra parentesi angolari come intestazioni esterne/external:W0
: Disabilita gli avvisi per le intestazioni esterne
Debugging
GDB
MSVC
Il debugger utilizzato da Visual Studio Code è abbastanza buono per la maggior parte delle attività di debugging.
Tuttavia, se hai bisogno di funzionalità più avanzate (ad es. un problema nel linking di una libreria esterna), puoi utilizzare il debugger di Visual Studio.
Per farlo, apri il Developer Command Prompt per Visual Studio (View > Terminal
) ed esegui il seguente comando:
devenv /DebugExe <path_to_executable>
Si aprirà una nuova istanza di Visual Studio e potrai eseguire il debug dell’eseguibile come faresti in Visual Studio Code.