Compilar i executar
Del codi C++ a l'executable: compilar, enllaçar i executar. També aprendràs a provar-lo amb fitxers, redireccions i diff.
Conceptes clau
- Preprocessar, compilar i enllaçar:
.cc→.o→.exe - Opcions del Jutge:
-Werror,-D_GLIBCXX_DEBUG… - Executar amb un camí:
./prog.exe - Canals estàndard i redireccions:
<,>,>>,2>,| - Encadenar comandes amb
&&i comparar ambdiff - Empaquetar amb
tar cfitar xf
En què consisteix compilar
El processador només entén codi màquina: instruccions binàries molt simples (copia aquest valor a un registre, suma dos registres, salta a aquesta adreça) i diferents per a cada arquitectura (x86-64, ARM…).
En canvi, nosaltres escrivim el codi en C++, un llenguatge pensat perquè l’entenguin les persones.
Per poder executar el programa, primer cal traduir-lo a codi màquina. Aquesta traducció la fa un programa: el compilador.
C++ és un llenguatge compilat: la traducció es fa una vegada, abans d’executar, i en surt un fitxer executable: el programa en codi màquina.
El compilador que farem servir, g++, llegeix el teu codi i, per arribar a l’executable, fa dues feines diferents: compilar i enllaçar.
Sovint es diu «compilar» a tot el procés, però en sentit estricte és només la primera feina.
1. Compilar
prog.cc en surt prog.o, i els #include hi copien les capçaleres.Compilar consisteix en agafar un fitxer font .cc i traduir-lo a codi màquina.
Per fer-ho cal executar aquesta comanda, que crea un .o amb el mateix nom que el .cc:
g++ -c <fitxer.cc>
El compilador per dins ho fa en tres fases: preprocessar, comprovar i traduir.
1.1. Preprocessar
Les línies #include del principi del programa indiquen quines biblioteques i fitxers externs fas servir.
Per saber com són les funcions i els objectes que en fas servir (com es diuen, quins paràmetres reben, què retornen), el compilador consulta les seves capçaleres o headers: fitxers que descriuen les funcions, però que no en porten el codi.
cout, per exemple, el descriu la capçalera<iostream>, que forma part de la biblioteca estàndard, el conjunt de codi que ve amb el compilador.
En preprocessar, el compilador busca cada línia #include i la substitueix pel contingut sencer de la capçalera.
Així, en la fase següent, el compilador ja sabrà com és cada funció i objecte que fa servir el codi, i podrà comprovar que s’utilitzen correctament.
1.2. Comprovar
Amb les capçaleres ja copiades, el compilador llegeix el codi de dalt a baix i revisa que es compleixin les regles del llenguatge: que la sintaxi és correcta, que els tipus encaixen i que tot el que fas servir està ben declarat.
- Com que ja sap com és
cout, pot comprovar que l’estàs fent servir bé.
Aquí és quan salten els errors de compilació: si alguna cosa no compleix les regles, el compilador s’atura i t’avisa amb un error.
1.3. Traduir
Un cop comprovat que el codi està ben escrit, el compilador el tradueix a instruccions de codi màquina.
En surt un fitxer objecte, .o, amb les funcions del .cc ja traduïdes.
Alerta: aquest .o encara no es pot executar. El codi de les funcions i objectes que vénen de fora és en altres fitxers. Les capçaleres només deien com s’utilitzen, perquè el compilador pogués fer la comprovació.
- El codi de
coutno hi és. Al seu lloc, el compilador hi deixa un forat: una referència indefinida (undefined reference), que vol dir «aquí vacout; ja em diran on és el codi».
Els forats només es poden omplir quan se sap on són tots els trossos. D’això se n’encarrega el pas següent: enllaçar.
2. Enllaçar
prog.o s'ajunta amb la biblioteca estàndard i amb els altres fitxers .o del programa.Enllaçar és ajuntar el teu .o amb la biblioteca estàndard (on hi ha el codi ja compilat de cout, string, vector…) i amb els altres fitxers .o que necessiti, per fer-ne l’executable: un sol fitxer amb tot el codi.
Ho fa el linker, que g++ crida automàticament quan li dones fitxers .o:
g++ -o <executable> <fitxers.o>
La seva feina és omplir les referències indefinides que han deixat els .o amb l’adreça del codi que els correspon.
Per saber com omplir-les, cada .o porta una taula de símbols amb dues llistes:
- els noms que defineix: les seves funcions i variables globals;
- els noms que necessita d’altres fitxers: les seves referències indefinides.
Per a cada nom que necessita un .o, el linker busca quin altre fitxer el defineix, i omple el forat amb l’adreça on es troba el codi.
Si un nom que es necessita no es defineix enlloc, el linker no pot omplir el forat i dona l’error undefined reference. El fitxer s’havia compilat bé, però en ajuntar-ho tot hi falta una peça.
prog.exe. Compiles cada cop que canvies el codi; executes tantes vegades com vulguis.Compilació a la pràctica amb g++
Ja hem vist una comanda per a cada pas:
g++ -c <fitxer.cc> # compilar: crea fitxer.o
g++ -o <executable> <fitxers.o> # enllaçar: crea l'executable
Però a la comanda d’enllaçar també li pots donar fitxers .cc directament.
Llavors g++ fa els dos passos seguits: primer compila cada .cc en un .o temporal (que no queda a la carpeta) i després ho enllaça tot.
Per això la forma general és aquesta:
g++ [opcions] -o <executable> <fitxers>
| Part | Què és |
|---|---|
g++ |
La comanda de GCC per compilar i enllaçar programes C++. |
[opcions] |
Com ha de compilar: quina versió del llenguatge, quins avisos, si ha d’afegir informació per depurar… Cada opció comença amb - i l’ordre entre elles no importa. |
-o <executable> |
El nom del fitxer executable que es crearà. Compte: si ja existeix se sobreescriu. |
<fitxers> |
Els fitxers font .cc que cal compilar i els .o que cal enllaçar. Es poden barrejar. |
g++ -o prog.exe prog.cc, fas els dos primers de cop.Amb molts fitxers, convé fer els dos passos per separat, perquè així només recompiles el que has canviat. Ho veuràs al capítol següent amb el Makefile.
- Si
g++no diu res, ha anat bé. Si falla, surten errors i no tens un executable nou. Per tant si ja n’hi havia un de vell, encara hi serà.
Executar
Executar és activar el fitxer executable perquè l’ordinador en segueixi les instruccions i en surti el resultat.
prog.exe i obtenir-ne el resultat.Per tal d’executar un fitxer cal escriure:
./<executable>
El ./ és el camí fins al fitxer: el . vol dir «la carpeta on ets» i la / separa la carpeta del nom.
- Així,
./prog.exevol dir «el fitxerprog.exed’aquesta carpeta».
Si en canvi escriusprog.exesol, el terminal el busca només a les carpetes del sistema, no el trobarà i respondrà ambcommand not found.
Si l’executable és en una altra carpeta, el pots executar posant-hi el seu camí:
sessio1/prog.exe # el prog.exe de la subcarpeta sessio1
Si escrius les dades a mà, pots acabar l’entrada amb Ctrl+D (a Windows, Ctrl+Z i Retorn) així el programa rep «final de fitxer» i el while (cin >> x) s’acaba.
Si el programa s’ha penjat, per exemple en un bucle infinit, el pots aturar amb Ctrl+C.
Redireccions
Fins ara, cada vegada que proves el programa has d’escriure les dades a mà. Amb les redireccions pots fer que les llegeixi d’un fitxer i que escrigui el resultat en un altre, i així no cal que hagis d’escriure les dades d’entrada cada vegada.
Els tres canals
Tot programa que engegues des del terminal té tres canals oberts des del primer moment:
| Canal | Número | En C++ | Per defecte va a… |
|---|---|---|---|
| Entrada estàndard (stdin) | 0 | cin |
el teclat |
| Sortida estàndard (stdout) | 1 | cout |
la pantalla |
| Sortida d’errors (stderr) | 2 | cerr |
la pantalla |
El programa no sap què hi ha a l’altra banda de cada canal: cin >> x llegeix del canal 0, i prou. Qui decideix on va connectat cada canal és el terminal, abans d’engegar el programa.
Una redirecció és demanar-li que connecti un canal a un fitxer en lloc del teclat o la pantalla. El codi no canvia gens.
./prog.exe < prog.in > prog.out: el canal 0 llegeix de prog.in i l'1 escriu a prog.out. El 2 continua anant a la pantalla.Llegir d’un fitxer: <
./prog.exe < prog.in
Ara cin llegeix de prog.in. Quan el fitxer s’acaba, cin arriba al final igual que quan prems Ctrl+D. La sortida continua apareixent a la pantalla.
Un exemple, amb un programa que suma tots els nombres de l’entrada:
#include <iostream>
using namespace std;
int main() {
int x, suma = 0;
while (cin >> x) suma += x;
cout << "Suma: " << suma << endl;
}
$ cat prog.in
3 5
10 2
$ ./prog.exe < prog.in
Suma: 20
Escriure a un fitxer: > i >>
./prog.exe > prog.out
Tot el que el programa escriu amb cout va a prog.out, i a la pantalla no surt res. Si el fitxer no existeix, es crea. Si existeix, es buida primer.
Amb >> la sortida s’afegeix al final del fitxer, en lloc de buidar-lo:
./prog.exe < prova1.in > totes.out # totes.out només té la sortida de la prova 1
./prog.exe < prova2.in >> totes.out # ara té la de la 1 i, a sota, la de la 2
Les dues coses alhora
./prog.exe < prog.in > prog.out
Llegeix de prog.in i escriu a prog.out. L’ordre de les redireccions no importa: ./prog.exe > prog.out < prog.in fa el mateix.
Els errors a part: 2>
cerr escriu al canal 2, i > no el toca. Per això els missatges que escrius amb cerr (i els de _GLIBCXX_DEBUG quan el programa peta) continuen sortint per pantalla encara que redirigeixis la sortida, i no embruten el fitxer que després compararàs. Si els vols en un fitxer, 2>:
cerr << "llegit: " << x << endl; // una traça per depurar
./prog.exe < prog.in > prog.out # la traça surt per pantalla; prog.out queda net
./prog.exe < prog.in > prog.out 2> traca.txt # la traça va a traca.txt
El Jutge només mira la sortida estàndard. Si depures amb cerr, una traça oblidada no et farà fallar la prova (però alenteix el programa: treu-la quan acabis).
Connectar dos programes: |
La canonada (pipe) connecta la sortida d’un programa amb l’entrada del següent, sense cap fitxer entremig:
echo "3 5 10 2" | ./prog.exe # unes dades ràpides sense crear cap fitxer
./prog.exe < prog.in | less # una sortida llarga, pàgina a pàgina
Comparar amb el resultat esperat
A tots els exercicis hi haurà jocs de proves amb un fitxer amb la sortida esperada. Pots comparar la teva sortida amb l’esperada fàcilment amb la comanda diff:
./prog.exe < prog.in > prog.out
diff prog.out esperat.out
Si diff no escriu res, les dues sortides són idèntiques.
Si escriu línies, són les que difereixen (< és la teva, > l’esperada).
Tot en una línia
Pots posar diverses comandes a la mateixa línia amb &&: la segona només s’executa si la primera ha anat bé. Així, si hi ha errors de compilació, no proves l’executable vell.
# compilar i enllaçar
g++ -c prog.cc && g++ -o prog.exe prog.o
# executar i comparar
./prog.exe < prog.in > prog.out && diff prog.out esperat.out
# tot
g++ -o prog.exe prog.cc && ./prog.exe < prog.in > prog.out && diff prog.out esperat.out
És la línia que repetiràs mil vegades: amb la fletxa amunt del terminal la recuperes.
Empaquetar fitxers
Per lliurar diversos fitxers junts es fa un .tar: un sol fitxer que en conté d’altres.
No els comprimeix, només els ajunta.
# crea el tar amb els fitxers que li diguis (c = create, f = file)
tar cf <paquet.tar> <fitxers>
# crea el tar amb tot el que hi ha a la carpeta
tar cf <paquet.tar> *
# extreu els fitxers del tar (x = extract, f = file)
tar xf <paquet.tar>
Quan hi ha molts fitxers
Escriure a mà totes aquestes comandes es fa pesat però amb dos o tres fitxers encara es pot fer.
Amb deu, recordar quins has canviat i quins .o cal refer és una liada.
Això ho podem simplificar fent un Makefile, un fitxer on apuntes com es fa cada cosa i de què depèn, i la comanda make només refà el que ha canviat.